# dynaskew draft [TOC] actor model 如何和 csp 融合 現在,還是需要actor model 的小 actor 去記錄每一行?(用hash map?) - [petri_flow](https://github.com/hooopo/petri_flow) - ruby - [A C++ Petri Net Framework For Multithreaded Programming](https://accu.org/journals/overload/11/54/nadle_357/) - 論文 - 太久以前 - [ PTN-Engine](https://github.com/vldtecno/PTN-Engine/blob/master/Doc/User%20Guide/UserGuide.md) - 不是很完整,有雛形。 --- blocking function 能否視為 non blocking function 的 special case? 之前implement nb, b function 時,執行都只有唯一的一個function,在不同時間執行。只有nb 多一個function 去check success。現在用 observer mode 不需要了。 如果我的task 兩個時間點都能做事反而是顯得很混亂。 nb func 加了 if condition 之後(select),會變得反而像b func一點,如何處理? 其實和 blocking 還是不一樣,比如 blocking func 可能是會讓prepare flow 穿過,但nb function with condition 不會。 --- ## petri net loop 1. function call 2. condition 3. loop loop(3) 肯定要 1, 2 才能構成 最少的特殊 node ### function 以下這個做法其實更像是inline function 或說 function 的展開。 ```lua! local head = b_spawned(2, {start}, "b_dwell_0_5") local head2 = nb_spawned(1, {head}, "g01({2,3},{2.1,3.4})") local head3 = b_spawned(1, {head}, "b_dwell(0.45)") local head4 = b_spawned(0, {head2, head3}, "(function(node) print(\"in lua call end\") end)") ``` to ```mermaid flowchart subgraph h2_multi_g01_func sh1-->sh2-->sh3 end h1-->sh1 h1-->h3 h3-->h4 sh3-->h4 ``` ```lua! function multi_g01(node) local h1=nb_spawned(1, {node}, "g01({2,3},{2.1,3.4})") local h2=nb_spawned(1, {h1}, "g01({2.1,3.4},{6.7,3.5})") local h3=nb_spawned(1, {h2}, "g01({6.7,3.5},{2.1,0.0})") return h3 end local head = b_spawned(2, {start}, "b_dwell_0_5") local head2 = multi_g01(head) local head3 = b_spawned(1, {head}, "b_dwell(0.45)") local head4 = b_spawned(0, {head2, head3}, "(function(node) print(\"in lua call end\") end)") ``` 要不要能讓 function have multi parent node, and multi return? 老實說,multi in 意義不大。 #### hierachy 這樣是有滿足條件了,但能不能有 hierachy 的形式? mermaid not correctly show ```mermaid flowchart subgraph h2_multi_g01_func sh1-->sh2-->sh3 end h1-->h2_multi_g01_func h1-->h3 h3-->h4 h2_multi_g01_func-->h4 %% mermaid not correctly show h2_multi_g01_func-->sh1 sh3-->h2_multi_g01_func ``` 恐怕不能用原來的 `b_spawned`,因為這個function 的 h2 的結尾要 prepare 時才會展開。這會導致h4 不知道要連接誰 (sh3還不存在)。h4肯定只能連h2。而"h2自己要想辦法在 sh3 prepare/resolve 時把訊號轉到 h4"。 相反,在h2 prepare 時, sh1, sh2, sh3 才被create。h2 才被 sh1 subscribe 。 這需要額外內部訂閱的特性,比如當進入 prepare state 才被 subscribe 了話,無條件trigger 這些 offspring,或是額外的 tot_inter_offspring_number。 回到 "h2自己要想辦法在 sh3 prepare/resolve 時把訊號轉到 h4",當sh3結束執行時,實際上的主動權是握在sh3,所要反而是它要有h2 的 pointer,把 signal傳過去。 node 可能因此需要加入 `wait_subroutine_calling` state ```lua! -- function that realy have concept of jump function jumped_multi_g01(node) -- node==h2 -- subscribe at h2 in prepare state local h1=nb_spawned(1, {node}, "g01({2,3},{2.1,3.4})") local h2=nb_spawned(1, {h1}, "g01({2.1,3.4},{6.7,3.5})") local h3=nb_spawned(1, {h2}, "g01({6.7,3.5},{2.1,0.0})") node->send(h3, caf::subscribe_atom_v, node) --lua may hard to call c++ function return --void end local head = b_spawned(2, {start}, "b_dwell_0_5") -- both inter parent(sh3) and inter offspring (sh1) are 1. local head2 = b_spawned(1, {head}, "jumped_multi_g01(head)", 1, 1) local head3 = b_spawned(1, {head}, "b_dwell(0.45)") local head4 = b_spawned(0, {head2, head3}, "(function(node) print(\"in lua call end\") end)") ``` hierachy 重要的是它多加了一個 向內縮的 維度。 這樣變成像是有一般程式的 function stack。 但這衍生了其他 implement 問題,`jumped_multi_g01` 是在 lua file 裡定義了話,新開的Lua state 如何 知道這個定義?可能就是要分檔案。或是定義 `main`(loop) function 就是 lua 執行點。自己在cpp 裡指定。 > gcode subroutine 目前也是這個系列的方法。在 檔案+行數跳轉。 #### implement 細節 - we can assume only 1 inner_offspring and 1 inner_parent_num - 進入function 後,由一個去串多個是可以做出各種功能,問題是會不會太繁瑣? - 其實大部份的function 很少需要 parallel。所以目前accept 這個預設。 - function 是在那一個時間點展開也是問題。e.g. 在prepare signal or resolve signal - 可以直接預設 nb node 就是 prepare 時展開,b_node 就是resolve。 - ### condition condition 在 node 層次沒問題,問題在lua 如何界接到cpp ```mermaid flowchart start-->h-->h2-->h6 h-->h3[[h3]] h3-->h4 h4-->h5-->h6 ``` ```lua! function if_run() return false end local head = b_spawned(2, {start}, "b_dwell_0_5") local head2 = nb_spawned(1, {head}, "g01({2,3},{2.1,3.4})") local head3 = b_spawned(1, {head}, "b_dwell(0.45)", "if_run") local head4 = b_spawned(1, {head3}, "b_dwell(0.45)") local head5 = b_spawned(1, {head4}, "b_dwell(0.45)") local head6 = b_spawned(0, {head2, head5}, "(function(node) print(\"in lua call end\") end)") ``` 一定要先建立 head3,4,5,才能建立head6。如果在head3 check condition 就停止讀取,會影響到後面node 的建立。只有建立,不傳prepare signal,而且如果失敗,必須要傳leave signal。 > 衍生一個問題,offsprig 需不需要告訴 parent 自己的task type,特別是 前者 b block,後者nb block時。這取決於 implement。 #### condition with hierachy hierachy 存在之後,condition 其實可以更好的多。原本的 condition 是都會先展開的,如果不執行就沒意義,還要用 leave event 去刪掉。如果等到發現真的需要做的時候再展開,那就能減少很多浪費。 所以有condition 的node 變得向 function call 展開。 ### while/loop ```mermaid flowchart start-->h-->h2-->h7 h-->h3_p[[h3_true]] h-->h3_n[[h3_false]] h4[h4_while_loop_start] h3_p-->h4-->h5 h5-->h h6-->h7-->normal_end[end] %%h5-->h3_p h3_n-->h6 ``` 如果flag 不成立直接跳到 h6 (ideal case),如果flag 成立則進入loop, h4, h5。 重點在 h 不能被free 掉。 還有 h5 是 h的上位,但,h5,最後才被create。所以上圖這種雖然完美,但如果不是 **完全不free node** 就實現不了。特別是 compile 了話,可以先在h給 h5 留空位。 free node 是 dynaskew 最大的feature ,卻也是最大的難度限制來源。 #### try implement 如果可以修改 b_spawned 的介面,不需要一開始就傳入所有parent 了話,還有機會。 再來,要記得就算h 不free 掉,sub node 被free 掉一樣就沒救,所以 h3_false 要設定成不能在初始被free 掉。h3_true 則是正常的leave。 當h3 接到 leave signal 時,因為來自h的signal 正常,所以是不會leave的。 那假設 h3_true,已經被執行過了。h4 已經 free self 了。--> 沒辦法做到。 ```mermaid flowchart subgraph h3_group h3 h3_p h3_n end start-->h-->h2-->h7 h-->h3-->h3_p[[h3_true]] h3-->h3_n{{h3_false}} h4[h4_while_loop_start] h3_p-->h4-->h5 h5-->h3 h6-->h7-->normal_end[end] %%h5-->h3_p h3_n-->h6 ``` ```lua function if_flag_false() return my_flag==false end function if_flag_true() return my_flag==true end local head = b_spawned(3, {start, nullptr}, "b_dwell_0_5") --h5 is nullptr local head2 = nb_spawned(1, {head}, "g01({2,3},{2.1,3.4})") local head3_false = b_spawned(1, {head}, "b_dwell(0.45)", "if_flag_false") local head3_true = b_spawned(1, {head}, "b_dwell(0.45)", "if_flag_true") local head6 = b_spawned(1, {head3_false}, "b_dwell(0.45)") local head7 = b_spawned(0, {head2, head6}, "(function(node) print(\"in lua call sub end\") end)") -- loop local head4 = b_spawned(1, {head3_true}, "b_dwell(0.45)", "if_flag_true") local head5 = b_spawned(1, {head4}, "b_dwell(0.45)") head5->send(h, subscribe_atom_v, head5); ``` 當h3_true 為 true 時,會自然的按現有的path 去執行。 要避免把 h3_false 都自動砍掉。 #### new ver - without hierachy function 首先無法無視的是如何處理不再是 DAG 的 loop。特別是主動權在執行方,但是執行完後會自動 free self 這件事。 只要不存在一個不自己free self的node,就無法提供堅實的基礎區分出哪裡是while 的開頭,會變得像是左腳踩右腳要求飛天,所以fixed node 必不可少。 while 迴圈本身視為一個 function 的跳轉 ```mermaid flowchart subgraph h2_while stone[/stone\] stone-->sh1-->sh2-->sh3 sh3 o--o stone end h1-->stone h1-->h3 h3-->h4 %%sh3-->h4 h2_while-->h4 ``` 如果想靠現成的while loop,就會touch 不到 其他節點。所以是不行用while的,而是要 node for while 。 ```lua! function while_h2(node) -- let first node don't free itself, --fixed node should with condition local stone_st=loop_spawned(1, {node}, "empty_func", "condi_func") -- local stone_st, stone_end=fix_spawned(1, {node}) local h1=fix_spawned(1, {stone_st}, "g01({2,3},{2.1,3.4})") local h2=nb_spawned(1, {h1}, "g01({2.1,3.4},{6.7,3.5})") local h3=nb_spawned(1, {h2}, "g01({6.7,3.5},{2.1,0.0})") stone_st->send(h3, subscribe_atom_v, stone_st) return stone_st end local head = b_spawned(2, {start}, "b_dwell_0_5") local head2 = while_h2(head) local head3 = b_spawned(1, {head}, "b_dwell(0.45)") local head4 = b_spawned(0, {head2, head3}, "(function(node) print(\"in lua call end\") end)") ``` stone 變成像是 head2 的 代理。stone 在condition true 時,send signal to h1 (可是這個時候 h1 也kill self 了?), while loop 還是必要,只是不能在 most shallow callstack create 時就call while。 -->無解。只能靠 call hierachy。 其實也能理解,不能 creation runtime 時 用while,又不能接無限次,肯定做不了。 #### new ver - with (not yet finish) ```mermaid flowchart subgraph h2_multi_g01_func h2_condi_func[[while_condi_func]] sh1-->sh2-->sh3 end h1-->h2_multi_g01_func h1-->h3 h3-->h4 h2_multi_g01_func-->h4 %% mermaid not correctly show h2_multi_g01_func-->sh1 sh3-->h2_multi_g01_func ``` node 剛好作為 fixed stone。 ```lua -- function that realy have concept of jump function jumped_multi_g01(node) -- node==h2 -- subscribe at h2 in prepare state -- while(h2_condi_func) local sh1=nb_spawned(1, {node}, "g01({2,3},{2.1,3.4})") local sh2=nb_spawned(1, {sh1}, "g01({2.1,3.4},{6.7,3.5})") local sh3=nb_spawned(1, {sh2}, "g01({6.7,3.5},{2.1,0.0})") node->send(sh3, caf::subscribe_atom_v, node) --lua may hard to call c++ function -- end return --void end local head = b_spawned(2, {start}, "b_dwell_0_5") -- both inter parent(sh3) and inter offspring (sh1) are 1. local head2 = b_spawned(1, {head}, "jumped_multi_g01(head)", 1, 1, "while_condi_func") local head3 = b_spawned(1, {head}, "b_dwell(0.45)") local head4 = b_spawned(0, {head2, head3}, "(function(node) print(\"in lua call end\") end)") ``` 當sh3 做完時發resolve 給 h2,h2 會再次先確定while condition func,若結果為true,會自動再call 一次 `jumped_multi_g01(head)` 。 ##### 用一個獨立的thread是一樣的 ![aaa.drawio](https://hackmd.io/_uploads/BkeVOfKAT.png) ### 反思 把一個語言的執行,重做一次。 gcode 時是直接控制 interpreter 解code,現在是? --- ## sol2 問題 lua_state 裡最重要的就是 lua stack, 是 virtual stack 作為 lua 和 C++ 之間的橋樑。 我認為 sol2 實際上的 calling 如下,實際上 lua function 是會作為夾層被call。當 multi thread 時,就會衝突。seg fault。 ```mermaid flowchart c_call-->lua_func-->c_ori_func ``` - [C++ multiple Lua states](https://stackoverflow.com/a/54549354) - > One exception to this rule would obviously be multi-threading; you shouldn't have more than one thread with access to the same Lua state unless you use some sort of locking mechanism, so it would make sense to have one state per thread and set up a way for them to communicate from within C. - [Chapter 1. Running a Lua Script from C](https://www.oreilly.com/library/view/creating-solid-apis/9781491986301/ch01.html) - > Lua’s C API can create or destroy entire runtime environments, which are referred to as Lua states. A Lua state is a C pointer to an opaque data structure. This structure knows everything about a running Lua environment, including all global values, closures, coroutines, and loaded modules. Virtually every function in Lua’s C API accepts a Lua state as its first parameter. - [State](https://poga.github.io/lua53-notes/state.html#state) - > There are two kinds of states a Lua VM keep track of while running: global_State and lua_State. - > Each Lua VM has a global_State. It keeps the information about GC metadata, metatables, and string cache. - > On the other hand, lua_State correspond to a Lua thread. The call stack of a given thread is also included in the corresponding lua_State. - [Can I make multiple Lua VM in single thread?](https://stackoverflow.com/a/6416679) - 看起來f lua_state 是完全隔開thread 了,但需要額外的執行器。 ### solution 兩個方法 1. 讓 create node 時就得到underline c function, so C++ caller --> C++ func 1. 無法在 lua 裡定義function 才讓C++ call, c++ caller --> lua func 2. method 1-1: hash map 從 string mapping to c++ function, pass in function string to node creation. 1. 太脫離lua/c++語言,一切自己來 2. method 1-2: lua function that return C++ function 1. 一進node create 就call function 去拿到 underline 的C++ function。 2. benefit 1. 好處當然是不用處理 lua function/bytecode... 如下一個方案,這絕對是超乎想像的困難。 1. 另外不需要再call lua 層 --> lower memory, higher speed. 最重要的是 multi thread 的問題。lua 本來就沒有任何multi thread 的功能,任何multi thread都可能導致詭異的bug,而且非常難debug 的 segfault,有跑unittest 但短時間測不出來,常常過了好幾commit才發現。回來searching 時,一個unittest 又要跑很久。 說到底真正的問題是 2. let the lua function able to multi thread really 1. method 2-1: copy original lua function into lua_state(each node seperately) 1. `lua_xmove`?, `sol::thread`? 3. [**threading**](https://sol2.readthedocs.io/en/latest/threading.html) 1. [coroutine](https://sol2.readthedocs.io/en/latest/api/coroutine.html?highlight=thread) 好像會自動產生新的 thread。 1. 看起來lua thread/coroutine 是不會work 的。因為一般情況下,我無法控制 actor 什麼時候會 call function,就算能也無法限制他要yield。 6. [ I would like to learn from You how to run a function in a separate thread? #853 ](https://github.com/ThePhD/sol2/issues/853#issuecomment-518947980) 7. [transferring functions (dumping bytecode)](https://sol2.readthedocs.io/en/latest/tutorial/all-the-things.html#transferring-functions-dumping-bytecode) 1. > You can dump the bytecode of a function, which allows you to transfer it to another state (or save it, or load it). Note that bytecode is typically specific to the Lua version! 1. method 2-2: 直接mutex 鎖死 lua state 1. 這個方法對於sleep 這種 task,應該是直接無法執行 ### method 2-1 1. proof modify to seperated lua state can solve this problem. 1. proof work, as expect. 2. if pass snippet string through parameter 3, which is combination with method 1-1 1. ~~有問題,不知道為什麼,一旦 lua b_spawn exe_function 的 lambda 接收的參數有string,caf 的 offspring actor ,好像就接收不到 resolve signal~~ 2. 後來發現只是 initial value 沒有設定。 3. weaknees 1. 最大的問題還是語法未經檢查。很有可能 runtime error。 1. lua 的 decode 變成在 multi thread runtime 才執行,這可能有讓couple 加強的問題,原來只要一個 lua state 有regist這些funtion就好,變成都要知道。這些 function dependency 可能因此需要coupling。 1. direct copy lua state (after regist all func) 系列 1. `lua_newthread` 產生的 lua state 是共用 global variable 的。lua_newstate 只能create 全新。 1. ~~如果call 在 global variable 的 function, 不會導致 母state multi write,就能使用。~~ 1. 測過一樣segfault。 1. 唯一完美的辦法是 serialize/deserialize,但不現實,因為不知道user datatype,特別是沒有reflection 時。 1. init pure new lua thread 1. through lambda function 1. 目前方案 2. 還可以,算沒有coupling。 3. Passing bytecode directly, is better than passing string. 1. [Issue migrating function from sol::state A to B](https://github.com/ThePhD/sol2/issues/786) 1. [How to transfer a luabind::object between two lua states](https://gamedev.stackexchange.com/a/62677) 1. serialize/deserialize 1. `sol::function.dump` ~~一直失敗的原因應該是 [lua_dump](https://www.lua.org/manual/5.3/manual.html#lua_dump), 所以只有當 sol function 剛被load 進現在的 lua state時,...~~。 1. 測試起來,如果是pure lua function 就沒問題。如果是 用 `set_function` 做出的c++ function 就有error。 2. 那如果把原本 set_function,先套一層 lua, 再call 這個function? 1. 雖然能夠 dump 成 bytecode,但是使用 `safe_script` deserialize 時,會直接call function,導致根本沒有 argument 傳入就calling ,因此 segmentation fault。 2. 方案失敗 目前看起來只能 pass string。 ### debug - `while ./runtest; do :; done` - [How can I rerun a program with gdb until a segmentation fault occurs?](https://stackoverflow.com/a/6546674) --- 確定要分task type了,要透過不同的actor class 分,還是單一task 不同的flag? 牽涉到lua 包的時候。手動的包CMF 當然是沒差。但是如果要直接coding 進ide 去產生對應的lua function了話,越是沒有branch,都是同一套流程就越好。 extend is similar to inherit in CAF 光是基於 typed actor 就有很大的問題,不同的type,就不能存在同一個actor 裡面 好像是 typed actor 和 extend compose typed actor 相差很大。extend 的 document 還很缺。 在wait_prepare state,get prepare calling 時,才展開sub node,才有wait prepare。 另外,當內部沒有loop condition時,接到inner_prepare就應該把prepare signal 往下outer offspring。 case: 1. x inner + x loop 2. 有 inner + x loop 1. normal function call 1. 合併進3? 要能自動改loop condi 3. 有 inner + x loop + 有條件 (condition) 1. if/else 2. b node only 4. 有 inner + 有 loop 1. while loop 2. b node only 而且要考慮到展開的時候。比如說 case 3 在 nb 是不support的。condition 只有在 block node 才support。否則inner loop都還沒執行就先往下執行,同理case 4。 但是光1,2怎麼分就是各問題。分法就是看有沒有inner_node value。 所以說,non-blocking node 其實不需要call style 的function。直接用inline function 就好。差別在 inline function 是 create time 就產生的,比較不會有展開時多出一堆 node 的 surprise 。圖畫出來就好,先不實做。 又發現bug,如果nb 的 inner 有 b node,那個b node 就會一直等待 resolve signal,但是 parent 的 NB 因為卡在wait prepare,所以沒辦法把拿到的wait_resolve signal 傳到inner 裡面。導致deadlock。 另外還要考慮,如果inner function 沒有把offspring 接到外部了話,一樣deadlock,不過這個可以用規範的,一開始就有說。 現在的重點就在於如何適當的把外部的 prepared, resolved signal ,在正確的時間丟給inner offspring。 這個bug 是因為我完全忘記 有inner 就一定會有 outer,我們現在的callstack 還可能有外層。我現在這一層如何傳給 自己的外層 是個問題。或是說,我現在把信號傳來傳去的做法還不是完整的 function call 跳轉,而是靠自己routing。 b node API 要如何區分?特別是 case 3 & case 4。while 的語意基本上是condition + loop. 所以要加上 loop, 多一個 bit 是跑不掉的。 展開方面,其實就像 call stack ,一樣,應該要限制每一個stack的展開數目,不展開執行不下去,non blocking 會被卡死。完全展開,會直接OOM。 如果是要展開 inter function,執行應該會卡到一半。導致母node 無法處理任何工作,接收還可以。是不是應該把工作 seperate 傳到額外的execute actor 去做。 ```mermaid --- title: nb node --- stateDiagram-v2 [*]-->wait_subscribe wait_subscribe-->wait_prepare:subscribe[all_subscribed] wait_prepare-->wait_inner_subscribe:prepared\n[inner_node_existed \n& all_prepared & condi_func ]\n/prnt+=1, offs+=1, prepare_func wait_inner_subscribe-->wait_prepare:[all_subscribed]\n/set condi_func false \n, send_inner_offsping_prepare wait_prepare-->wait_resolve:prepared\n[ inner_node_existed \n& !condi_func & all_prepared]\n/send_offspring_prepared wait_prepare-->wait_resolve:prepared\n[!inner_node_existed \n& all_prepared]\n/prepared_func, send_offspring_prepared wait_resolve-->[*]: resolve,ok \n[all_resolved & done]\n /send_offsping_resolved ``` ```mermaid --- title: b node --- stateDiagram-v2 [*]-->wait_subscribe wait_subscribe-->wait_resolve:subscribe\n[all_subscribed]\n wait_resolve-->wait_inner_subscribe: resolve \n[inner_node_existed & \ncondi_func & all_resolved]\n/prnt+=1, offs+=1, exe_func wait_inner_subscribe-->wait_resolve:[all_subscribed]\n/if no loop then remove condi_func wait_resolve-->[*]:resolve \n[inner_node_existed \n & !condi_func & all_resolved]\n/send_offsping_resolved wait_resolve-->[*]:resolve \n[!inner_node_existed \n& all_resolved ]\n/exe_func, send_offsping_resolved ``` - blocking func should block prepare signal until it exec or back prepare nb function may execute first. - inference that merge brnach need to wait until all pending signal receive, or the merge node may execute before b func. - The same, nb func can't be parallel or merge node, which may lead to unexpected order - e.g. for prallel case, if bypass pending signal to sub branch, sub branch may execute before parallel node . - for merge cass, if sub offspring node bypass pending signal, merged sub branch may execute before parent branch. - Therefore, only blocking function is allowed to have condition, which need to block pending and resolve signal, too.