diff options
-rw-r--r-- | CHANGES | 4 | ||||
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | docs/index.html | 47 | ||||
-rw-r--r-- | lanes-3.11-1.rockspec | 66 | ||||
-rw-r--r-- | lanes-3.14.0-0.rockspec (renamed from lanes-3.13.0-0.rockspec) | 6 | ||||
-rw-r--r-- | src/Makefile | 2 | ||||
-rw-r--r-- | src/cancel.c | 117 | ||||
-rw-r--r-- | src/cancel.h | 15 | ||||
-rw-r--r-- | src/lanes.c | 67 | ||||
-rw-r--r-- | src/lanes.h | 2 | ||||
-rw-r--r-- | src/lanes.lua | 17 | ||||
-rw-r--r-- | src/lanes_private.h | 3 | ||||
-rw-r--r-- | tests/basic.lua | 10 | ||||
-rw-r--r-- | tests/cancel.lua | 162 | ||||
-rw-r--r-- | tests/fifo.lua | 32 | ||||
-rw-r--r-- | tests/timer.lua | 2 |
16 files changed, 305 insertions, 251 deletions
@@ -1,5 +1,9 @@ | |||
1 | CHANGES: | 1 | CHANGES: |
2 | 2 | ||
3 | CHANGE 146: BGe 26-Apr-19 | ||
4 | * lane:cancel() rework (see doc). | ||
5 | * opt.cancelstep is gone, hook is installed by lane:cancel() if requested | ||
6 | |||
3 | CHANGE 145: BGe 28-Nov-18 | 7 | CHANGE 145: BGe 28-Nov-18 |
4 | * more code refacto | 8 | * more code refacto |
5 | * don't test __lanesignore for POD types (-> slightly faster when trasnfering lots of data) | 9 | * don't test __lanesignore for POD types (-> slightly faster when trasnfering lots of data) |
@@ -76,6 +76,7 @@ test: | |||
76 | $(MAKE) irayo_recursive | 76 | $(MAKE) irayo_recursive |
77 | $(MAKE) irayo_closure | 77 | $(MAKE) irayo_closure |
78 | $(MAKE) basic | 78 | $(MAKE) basic |
79 | $(MAKE) cancel | ||
79 | $(MAKE) fifo | 80 | $(MAKE) fifo |
80 | $(MAKE) keeper | 81 | $(MAKE) keeper |
81 | $(MAKE) timer | 82 | $(MAKE) timer |
@@ -94,6 +95,9 @@ test: | |||
94 | basic: tests/basic.lua $(_TARGET_SO) | 95 | basic: tests/basic.lua $(_TARGET_SO) |
95 | $(_PREFIX) $(LUA) $< | 96 | $(_PREFIX) $(LUA) $< |
96 | 97 | ||
98 | cancel: tests/cancel.lua $(_TARGET_SO) | ||
99 | $(_PREFIX) $(LUA) $< | ||
100 | |||
97 | # | 101 | # |
98 | # This tries to show out a bug which happens in lane cleanup (multicore CPU's only) | 102 | # This tries to show out a bug which happens in lane cleanup (multicore CPU's only) |
99 | # | 103 | # |
diff --git a/docs/index.html b/docs/index.html index db8e7a0..0b19223 100644 --- a/docs/index.html +++ b/docs/index.html | |||
@@ -64,13 +64,13 @@ | |||
64 | <font size="-1"> | 64 | <font size="-1"> |
65 | <p> | 65 | <p> |
66 | <br/> | 66 | <br/> |
67 | <i>Copyright © 2007-18 Asko Kauppi, Benoit Germain. All rights reserved.</i> | 67 | <i>Copyright © 2007-19 Asko Kauppi, Benoit Germain. All rights reserved.</i> |
68 | <br/> | 68 | <br/> |
69 | Lua Lanes is published under the same <a href="http://en.wikipedia.org/wiki/MIT_License">MIT license</a> as Lua 5.1, 5.2, and 5.3. | 69 | Lua Lanes is published under the same <a href="http://en.wikipedia.org/wiki/MIT_License">MIT license</a> as Lua 5.1, 5.2, 5.3 and 5.4. |
70 | </p> | 70 | </p> |
71 | 71 | ||
72 | <p> | 72 | <p> |
73 | This document was revised on 15-Nov-18, and applies to version <tt>3.13.0</tt>. | 73 | This document was revised on 26-Apr-19, and applies to version <tt>3.14.0</tt>. |
74 | </p> | 74 | </p> |
75 | </font> | 75 | </font> |
76 | </center> | 76 | </center> |
@@ -605,16 +605,6 @@ | |||
605 | </tr> | 605 | </tr> |
606 | <tr valign=top> | 606 | <tr valign=top> |
607 | <td> | 607 | <td> |
608 | <code>.cancelstep</code> | ||
609 | </td> | ||
610 | <td>integer >= 1/<tt>true</tt></td> | ||
611 | <td> | ||
612 | By default, lanes are only cancellable when they <u>enter</u> a pending <tt>:receive()</tt> or <tt>:send()</tt> call. With this option, one can set <a href="#cancelling">cancellation</a> check to occur every <tt>N</tt> Lua statements through the line hook facility. The value <tt>true</tt> uses a default value (100). | ||
613 | It is also possible to manually test for cancel requests with <tt>cancel_test()</tt>. | ||
614 | </td> | ||
615 | </tr> | ||
616 | <tr valign=top> | ||
617 | <td> | ||
618 | <code>.globals</code> | 608 | <code>.globals</code> |
619 | </td> | 609 | </td> |
620 | <td>table</td> | 610 | <td>table</td> |
@@ -949,32 +939,37 @@ | |||
949 | <h2 id="cancelling">Cancelling</h2> | 939 | <h2 id="cancelling">Cancelling</h2> |
950 | 940 | ||
951 | <table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> | 941 | <table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> |
952 | bool[,reason] = lane_h:cancel( [positive_timeout_secs=0.0] [, force_kill_bool = false] [, forcekill_timeout=0.0]) | 942 | bool[,reason] = lane_h:cancel( "soft" [, timeout] [, wake_bool]) |
953 | bool[,reason] = lane_h:cancel( negative_timeout_secs [, wake_bool = false]) | 943 | bool[,reason] = lane_h:cancel( "hard" [, timeout] [, force [, forcekill_timeout]]) |
944 | bool[,reason] = lane_h:cancel( [mode, hookcount] [, timeout] [, force [, forcekill_timeout]]) | ||
954 | </pre></td></tr></table> | 945 | </pre></td></tr></table> |
955 | 946 | ||
956 | <p> | 947 | <p> |
957 | <tt>cancel()</tt> sends a cancellation request to the lane.<br/> | 948 | <tt>cancel()</tt> sends a cancellation request to the lane.<br/> |
958 | First argument is a timeout, that defaults to 0 if not specified. (Starting with version 3.6.3) signification of the following arguments differ depending on whether the timeout is negative or not. | 949 | First argument is a <tt>mode</tt> can be one of <tt>"hard"</tt>, <tt>"soft"</tt>, <tt>"count"</tt>, <tt>"line"</tt>, <tt>"call"</tt>, <tt>"ret"</tt>. |
950 | If <tt>mode</tt> is not specified, it defaults to <tt>"hard"</tt>. | ||
959 | </p> | 951 | </p> |
960 | <p> | 952 | <p> |
961 | If <tt>timeout_secs</tt> is negative (aka "soft cancel"), cancellation will only cause <tt>cancel_test()</tt> to return <tt>true</tt>, so that the lane can cleanup manually (the actual value is irrelevant). | 953 | If <tt>mode</tt> is <tt>"soft"</tt>, cancellation will only cause <tt>cancel_test()</tt> to return <tt>true</tt>, so that the lane can cleanup manually.<br/> |
962 | If <tt>wake_bool</tt> is <tt>true</tt>, the lane is also signalled so that execution returns from any pending linda operation. Linda operations detecting the cancellation request return <tt>lanes.cancel_error</tt>. | 954 | If <tt>wake_bool</tt> is <tt>true</tt>, the lane is also signalled so that execution returns from any pending linda operation. Linda operations detecting the cancellation request return <tt>lanes.cancel_error</tt>. |
963 | </p> | 955 | </p> |
964 | <p> | 956 | <p> |
965 | If <tt>timeout_secs</tt> is positive (aka "hard cancel"), waits for the request to be processed, or a timeout to occur. Linda operations detecting the cancellation request will raise a special cancellation error (meaning they won't return in that case). | 957 | If <tt>mode</tt> is <tt>"hard"</tt>, waits for the request to be processed, or a timeout to occur. Linda operations detecting the cancellation request will raise a special cancellation error (meaning they won't return in that case).<br/> |
958 | <tt>timeout</tt> defaults to 0 if not specified. | ||
959 | </p> | ||
960 | <p> | ||
961 | Other values of <tt>mode</tt> will asynchronously install the corresponding hook, then behave as <tt>"hard"</tt>. | ||
962 | </p> | ||
963 | <p> | ||
966 | If <tt>force_kill_bool</tt> is <tt>true</tt>, <tt>forcekill_timeout</tt> can be set to tell how long lanes will wait for the OS thread to terminate before raising an error. Windows threads always terminate immediately, but it might not always be the case with some pthread implementations. | 964 | If <tt>force_kill_bool</tt> is <tt>true</tt>, <tt>forcekill_timeout</tt> can be set to tell how long lanes will wait for the OS thread to terminate before raising an error. Windows threads always terminate immediately, but it might not always be the case with some pthread implementations. |
967 | Returns <tt>true</tt> if soft cancelling, or the lane was already done (in <tt>"done"</tt>, <tt>"error"</tt> or <tt>"cancelled"</tt> status), or the cancellation was fruitful within <tt>timeout_secs</tt> timeout period. | ||
968 | </p> | 965 | </p> |
969 | |||
970 | <p> | 966 | <p> |
971 | If the lane is still running after the timeout expired and <tt>force_kill</tt> is <tt>true</tt>, the OS thread running the lane is forcefully killed. This means no GC, probable OS resource leaks (thread stack, locks, DLL notifications), and should generally be the last resort. | 967 | Returns <tt>true, lane_h.status</tt> if lane was already done (in <tt>"done"</tt>, <tt>"error"</tt> or <tt>"cancelled"</tt> status), or the cancellation was fruitful within <tt>timeout_secs</tt> timeout period.<br/> |
968 | Returns <tt>false, "timeout"</tt> otherwise. | ||
972 | </p> | 969 | </p> |
973 | |||
974 | <p> | 970 | <p> |
975 | Starting with v3.3.0, if <tt>cancel()</tt> returns false, it also returns either <tt>"timeout"</tt> or <tt>"killed"</tt> as second return value. | 971 | If the lane is still running after the timeout expired and <tt>force_kill</tt> is <tt>true</tt>, the OS thread running the lane is forcefully killed. This means no GC, probable OS resource leaks (thread stack, locks, DLL notifications), and should generally be the last resort. |
976 | </p> | 972 | </p> |
977 | |||
978 | <p> | 973 | <p> |
979 | Cancellation is tested <u>before</u> going to sleep in <tt>receive()</tt> or <tt>send()</tt> calls and after executing <tt>cancelstep</tt> Lua statements. Starting with version 3.0-beta, a pending <tt>receive()</tt>or <tt>send()</tt> call is awakened. | 974 | Cancellation is tested <u>before</u> going to sleep in <tt>receive()</tt> or <tt>send()</tt> calls and after executing <tt>cancelstep</tt> Lua statements. Starting with version 3.0-beta, a pending <tt>receive()</tt>or <tt>send()</tt> call is awakened. |
980 | <br/> | 975 | <br/> |
@@ -1732,7 +1727,9 @@ int luaD_new_clonable( lua_State* L) | |||
1732 | <h2 id="changes">Change log</h2> | 1727 | <h2 id="changes">Change log</h2> |
1733 | 1728 | ||
1734 | <p> | 1729 | <p> |
1735 | See CHANGES. | 1730 | v3.14.0: lane:cancel() rework: opt.cancelstep is gone, hook is installed by lane:cancel() if requested. |
1731 | |||
1732 | For older stuff see CHANGES. | ||
1736 | </p> | 1733 | </p> |
1737 | 1734 | ||
1738 | <!-- footnotes +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> | 1735 | <!-- footnotes +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> |
diff --git a/lanes-3.11-1.rockspec b/lanes-3.11-1.rockspec deleted file mode 100644 index fde5878..0000000 --- a/lanes-3.11-1.rockspec +++ /dev/null | |||
@@ -1,66 +0,0 @@ | |||
1 | -- | ||
2 | -- Lanes rockspec | ||
3 | -- | ||
4 | -- Ref: | ||
5 | -- <http://luarocks.org/en/Rockspec_format> | ||
6 | -- | ||
7 | |||
8 | package = "Lanes" | ||
9 | |||
10 | version = "3.11-1" | ||
11 | |||
12 | source= { | ||
13 | url= "git://github.com/LuaLanes/lanes.git", | ||
14 | branch= "v3.11" | ||
15 | } | ||
16 | |||
17 | description = { | ||
18 | summary= "Multithreading support for Lua", | ||
19 | detailed= [[ | ||
20 | Lua Lanes is a portable, message passing multithreading library | ||
21 | providing the possibility to run multiple Lua states in parallel. | ||
22 | ]], | ||
23 | license= "MIT/X11", | ||
24 | homepage="https://github.com/LuaLanes/lanes", | ||
25 | maintainer="Benoit Germain <bnt.germain@gmail.com>" | ||
26 | } | ||
27 | |||
28 | -- Q: What is the difference of "windows" and "win32"? Seems there is none; | ||
29 | -- so should we list either one or both? | ||
30 | -- | ||
31 | supported_platforms= { "win32", | ||
32 | "macosx", | ||
33 | "linux", | ||
34 | "freebsd", -- TBD: not tested | ||
35 | "msys", -- TBD: not supported by LuaRocks 1.0 (or is it?) | ||
36 | } | ||
37 | |||
38 | dependencies= { | ||
39 | "lua >= 5.1", -- builds with either 5.1, 5.2 and 5.3 | ||
40 | } | ||
41 | |||
42 | build = { | ||
43 | type = "builtin", | ||
44 | platforms = | ||
45 | { | ||
46 | linux = | ||
47 | { | ||
48 | modules = | ||
49 | { | ||
50 | ["lanes.core"] = | ||
51 | { | ||
52 | libraries = "pthread" | ||
53 | }, | ||
54 | } | ||
55 | } | ||
56 | }, | ||
57 | modules = | ||
58 | { | ||
59 | ["lanes.core"] = | ||
60 | { | ||
61 | sources = { "src/compat.c", "src/deep.c", "src/lanes.c", "src/keeper.c", "src/tools.c", "src/threading.c"}, | ||
62 | incdirs = { "src"}, | ||
63 | }, | ||
64 | lanes = "src/lanes.lua" | ||
65 | } | ||
66 | } | ||
diff --git a/lanes-3.13.0-0.rockspec b/lanes-3.14.0-0.rockspec index dec529e..8d56b80 100644 --- a/lanes-3.13.0-0.rockspec +++ b/lanes-3.14.0-0.rockspec | |||
@@ -7,11 +7,11 @@ | |||
7 | 7 | ||
8 | package = "Lanes" | 8 | package = "Lanes" |
9 | 9 | ||
10 | version = "3.13.0-0" | 10 | version = "3.14.0-0" |
11 | 11 | ||
12 | source= { | 12 | source= { |
13 | url= "git://github.com/LuaLanes/lanes.git", | 13 | url= "git://github.com/LuaLanes/lanes.git", |
14 | branch= "v3.13.0" | 14 | branch= "v3.14.0" |
15 | } | 15 | } |
16 | 16 | ||
17 | description = { | 17 | description = { |
@@ -58,7 +58,7 @@ build = { | |||
58 | { | 58 | { |
59 | ["lanes.core"] = | 59 | ["lanes.core"] = |
60 | { | 60 | { |
61 | sources = { "src/compat.c", "src/deep.c", "src/lanes.c", "src/linda.c", "src/keeper.c", "src/tools.c", "src/threading.c", "src/universe.c"}, | 61 | sources = { "src/cancel.c", "src/compat.c", "src/deep.c", "src/lanes.c", "src/linda.c", "src/keeper.c", "src/tools.c", "src/threading.c", "src/universe.c"}, |
62 | incdirs = { "src"}, | 62 | incdirs = { "src"}, |
63 | }, | 63 | }, |
64 | lanes = "src/lanes.lua" | 64 | lanes = "src/lanes.lua" |
diff --git a/src/Makefile b/src/Makefile index aff06ba..9dc75a1 100644 --- a/src/Makefile +++ b/src/Makefile | |||
@@ -7,7 +7,7 @@ | |||
7 | 7 | ||
8 | MODULE=lanes | 8 | MODULE=lanes |
9 | 9 | ||
10 | SRC=lanes.c compat.c threading.c tools.c linda.c deep.c keeper.c universe.c | 10 | SRC=lanes.c cancel.c compat.c threading.c tools.c linda.c deep.c keeper.c universe.c |
11 | 11 | ||
12 | OBJ=$(SRC:.c=.o) | 12 | OBJ=$(SRC:.c=.o) |
13 | 13 | ||
diff --git a/src/cancel.c b/src/cancel.c index 11e100d..8ce00c8 100644 --- a/src/cancel.c +++ b/src/cancel.c | |||
@@ -77,12 +77,13 @@ LUAG_FUNC( cancel_test) | |||
77 | // ################################################################################################ | 77 | // ################################################################################################ |
78 | // ################################################################################################ | 78 | // ################################################################################################ |
79 | 79 | ||
80 | void cancel_hook( lua_State* L, lua_Debug* ar) | 80 | static void cancel_hook( lua_State* L, lua_Debug* ar) |
81 | { | 81 | { |
82 | (void)ar; | 82 | (void)ar; |
83 | DEBUGSPEW_CODE( fprintf( stderr, "cancel_hook\n")); | 83 | DEBUGSPEW_CODE( fprintf( stderr, "cancel_hook\n")); |
84 | if( cancel_test( L) != CANCEL_NONE) | 84 | if( cancel_test( L) != CANCEL_NONE) |
85 | { | 85 | { |
86 | lua_sethook( L, NULL, 0, 0); | ||
86 | cancel_error( L); | 87 | cancel_error( L); |
87 | } | 88 | } |
88 | } | 89 | } |
@@ -110,10 +111,10 @@ void cancel_hook( lua_State* L, lua_Debug* ar) | |||
110 | 111 | ||
111 | // ################################################################################################ | 112 | // ################################################################################################ |
112 | 113 | ||
113 | static cancel_result thread_cancel_soft( Lane* s, bool_t wake_lindas_) | 114 | static cancel_result thread_cancel_soft( Lane* s, double secs_, bool_t wake_lindas_) |
114 | { | 115 | { |
115 | s->cancel_request = CANCEL_SOFT; // it's now signaled to stop | 116 | s->cancel_request = CANCEL_SOFT; // it's now signaled to stop |
116 | // negative timeout: we don't want to truly abort the lane, we just want it to react to cancel_test() on its own | 117 | // negative timeout: we don't want to truly abort the lane, we just want it to react to cancel_test() on its own |
117 | if( wake_lindas_) // wake the thread so that execution returns from any pending linda operation if desired | 118 | if( wake_lindas_) // wake the thread so that execution returns from any pending linda operation if desired |
118 | { | 119 | { |
119 | SIGNAL_T *waiting_on = s->waiting_on; | 120 | SIGNAL_T *waiting_on = s->waiting_on; |
@@ -122,8 +123,8 @@ static cancel_result thread_cancel_soft( Lane* s, bool_t wake_lindas_) | |||
122 | SIGNAL_ALL( waiting_on); | 123 | SIGNAL_ALL( waiting_on); |
123 | } | 124 | } |
124 | } | 125 | } |
125 | // say we succeeded though | 126 | |
126 | return CR_Cancelled; | 127 | return THREAD_WAIT( &s->thread, secs_, &s->done_signal, &s->done_lock, &s->status) ? CR_Cancelled : CR_Timeout; |
127 | } | 128 | } |
128 | 129 | ||
129 | // ################################################################################################ | 130 | // ################################################################################################ |
@@ -163,9 +164,9 @@ static cancel_result thread_cancel_hard( lua_State* L, Lane* s, double secs_, bo | |||
163 | (void) waitkill_timeout_; // unused | 164 | (void) waitkill_timeout_; // unused |
164 | (void) L; // unused | 165 | (void) L; // unused |
165 | #endif // THREADAPI == THREADAPI_PTHREAD | 166 | #endif // THREADAPI == THREADAPI_PTHREAD |
166 | s->mstatus = KILLED; // mark 'gc' to wait for it | 167 | s->mstatus = KILLED; // mark 'gc' to wait for it |
167 | // note that s->status value must remain to whatever it was at the time of the kill | 168 | // note that s->status value must remain to whatever it was at the time of the kill |
168 | // because we need to know if we can lua_close() the Lua State or not. | 169 | // because we need to know if we can lua_close() the Lua State or not. |
169 | result = CR_Killed; | 170 | result = CR_Killed; |
170 | } | 171 | } |
171 | return result; | 172 | return result; |
@@ -173,7 +174,7 @@ static cancel_result thread_cancel_hard( lua_State* L, Lane* s, double secs_, bo | |||
173 | 174 | ||
174 | // ################################################################################################ | 175 | // ################################################################################################ |
175 | 176 | ||
176 | cancel_result thread_cancel( lua_State* L, Lane* s, double secs_, bool_t force_, double waitkill_timeout_) | 177 | cancel_result thread_cancel( lua_State* L, Lane* s, CancelOp op_, double secs_, bool_t force_, double waitkill_timeout_) |
177 | { | 178 | { |
178 | // remember that lanes are not transferable: only one thread can cancel a lane, so no multithreading issue here | 179 | // remember that lanes are not transferable: only one thread can cancel a lane, so no multithreading issue here |
179 | // We can read 's->status' without locks, but not wait for it (if Posix no PTHREAD_TIMEDJOIN) | 180 | // We can read 's->status' without locks, but not wait for it (if Posix no PTHREAD_TIMEDJOIN) |
@@ -190,9 +191,9 @@ cancel_result thread_cancel( lua_State* L, Lane* s, double secs_, bool_t force_, | |||
190 | 191 | ||
191 | // signal the linda the wake up the thread so that it can react to the cancel query | 192 | // signal the linda the wake up the thread so that it can react to the cancel query |
192 | // let us hope we never land here with a pointer on a linda that has been destroyed... | 193 | // let us hope we never land here with a pointer on a linda that has been destroyed... |
193 | if( secs_ < 0.0) | 194 | if( op_ == CO_Soft) |
194 | { | 195 | { |
195 | return thread_cancel_soft( s, force_); | 196 | return thread_cancel_soft( s, secs_, force_); |
196 | } | 197 | } |
197 | 198 | ||
198 | return thread_cancel_hard( L, s, secs_, force_, waitkill_timeout_); | 199 | return thread_cancel_hard( L, s, secs_, force_, waitkill_timeout_); |
@@ -201,49 +202,97 @@ cancel_result thread_cancel( lua_State* L, Lane* s, double secs_, bool_t force_, | |||
201 | // ################################################################################################ | 202 | // ################################################################################################ |
202 | // ################################################################################################ | 203 | // ################################################################################################ |
203 | 204 | ||
204 | // lane_h:cancel( [timeout] [, force [, forcekill_timeout]]) | 205 | // > 0: the mask |
206 | // = 0: soft | ||
207 | // < 0: hard | ||
208 | static CancelOp which_op( lua_State* L, int idx_) | ||
209 | { | ||
210 | if( lua_type( L, idx_) == LUA_TSTRING) | ||
211 | { | ||
212 | CancelOp op = CO_Invalid; | ||
213 | char const* str = lua_tostring( L, idx_); | ||
214 | if( strcmp( str, "soft") == 0) | ||
215 | { | ||
216 | op = CO_Soft; | ||
217 | } | ||
218 | else if( strcmp( str, "count") == 0) | ||
219 | { | ||
220 | op = CO_Count; | ||
221 | } | ||
222 | else if( strcmp( str, "line") == 0) | ||
223 | { | ||
224 | op = CO_Line; | ||
225 | } | ||
226 | else if( strcmp( str, "call") == 0) | ||
227 | { | ||
228 | op = CO_Call; | ||
229 | } | ||
230 | else if( strcmp( str, "ret") == 0) | ||
231 | { | ||
232 | op = CO_Ret; | ||
233 | } | ||
234 | else if( strcmp( str, "hard") == 0) | ||
235 | { | ||
236 | op = CO_Hard; | ||
237 | } | ||
238 | lua_remove( L, idx_); // argument is processed, remove it | ||
239 | if( op == CO_Invalid) | ||
240 | { | ||
241 | luaL_error( L, "invalid hook option %s", str); | ||
242 | } | ||
243 | return op; | ||
244 | } | ||
245 | return CO_Hard; | ||
246 | } | ||
247 | // ################################################################################################ | ||
248 | |||
249 | // bool[,reason] = lane_h:cancel( [mode, hookcount] [, timeout] [, force [, forcekill_timeout]]) | ||
205 | LUAG_FUNC( thread_cancel) | 250 | LUAG_FUNC( thread_cancel) |
206 | { | 251 | { |
207 | Lane* s = lua_toLane( L, 1); | 252 | Lane* s = lua_toLane( L, 1); |
208 | double secs = 0.0; | 253 | double secs = 0.0; |
209 | int force_i = 2; | 254 | CancelOp op = which_op( L, 2); // this removes the op string from the stack |
210 | int forcekill_timeout_i = 3; | ||
211 | 255 | ||
212 | if( lua_isnumber( L, 2)) | 256 | if( op > 0) // hook is requested |
213 | { | 257 | { |
214 | secs = lua_tonumber( L, 2); | 258 | int hook_count = (int) lua_tointeger( L, 2); |
215 | if( secs < 0.0 && lua_gettop( L) > 3) | 259 | lua_remove( L, 2); // argument is processed, remove it |
260 | if( hook_count < 1) | ||
216 | { | 261 | { |
217 | return luaL_error( L, "can't force_kill a soft cancel"); | 262 | return luaL_error( L, "hook count cannot be < 1"); |
218 | } | 263 | } |
219 | // negative timeout and force flag means we want to wake linda-waiting threads | 264 | lua_sethook( s->L, cancel_hook, op, hook_count); |
220 | ++ force_i; | ||
221 | ++ forcekill_timeout_i; | ||
222 | } | 265 | } |
223 | else if( lua_isnil( L, 2)) | 266 | |
267 | if( lua_type( L, 2) == LUA_TNUMBER) | ||
224 | { | 268 | { |
225 | ++ force_i; | 269 | secs = lua_tonumber( L, 2); |
226 | ++ forcekill_timeout_i; | 270 | lua_remove( L, 2); // argument is processed, remove it |
271 | if( secs < 0.0) | ||
272 | { | ||
273 | return luaL_error( L, "cancel timeout cannot be < 0"); | ||
274 | } | ||
227 | } | 275 | } |
228 | 276 | ||
229 | { | 277 | { |
230 | bool_t force = lua_toboolean( L, force_i); // FALSE if nothing there | 278 | bool_t force = lua_toboolean( L, 2); // FALSE if nothing there |
231 | double forcekill_timeout = luaL_optnumber( L, forcekill_timeout_i, 0.0); | 279 | double forcekill_timeout = luaL_optnumber( L, 3, 0.0); |
232 | 280 | ||
233 | switch( thread_cancel( L, s, secs, force, forcekill_timeout)) | 281 | switch( thread_cancel( L, s, op, secs, force, forcekill_timeout)) |
234 | { | 282 | { |
235 | case CR_Timeout: | 283 | case CR_Timeout: |
236 | lua_pushboolean( L, 0); | 284 | lua_pushboolean( L, 0); |
237 | lua_pushstring( L, "timeout"); | 285 | lua_pushstring( L, "timeout"); |
238 | return 2; | 286 | return 2; |
239 | 287 | ||
240 | case CR_Cancelled: | 288 | case CR_Cancelled: |
241 | lua_pushboolean( L, 1); | 289 | lua_pushboolean( L, 1); |
242 | return 1; | 290 | push_thread_status( L, s); |
291 | return 2; | ||
243 | 292 | ||
244 | case CR_Killed: | 293 | case CR_Killed: |
245 | lua_pushboolean( L, 0); | 294 | lua_pushboolean( L, 1); |
246 | lua_pushstring( L, "killed"); | 295 | push_thread_status( L, s); |
247 | return 2; | 296 | return 2; |
248 | } | 297 | } |
249 | } | 298 | } |
diff --git a/src/cancel.h b/src/cancel.h index 3112809..e7656ac 100644 --- a/src/cancel.h +++ b/src/cancel.h | |||
@@ -29,15 +29,24 @@ typedef enum | |||
29 | CR_Killed | 29 | CR_Killed |
30 | } cancel_result; | 30 | } cancel_result; |
31 | 31 | ||
32 | typedef enum | ||
33 | { | ||
34 | CO_Invalid = -2, | ||
35 | CO_Hard = -1, | ||
36 | CO_Soft = 0, | ||
37 | CO_Count = LUA_MASKCOUNT, | ||
38 | CO_Line = LUA_MASKLINE, | ||
39 | CO_Call = LUA_MASKCALL, | ||
40 | CO_Ret = LUA_MASKRET, | ||
41 | } CancelOp; | ||
42 | |||
32 | // crc64/we of string "CANCEL_ERROR" generated at http://www.nitrxgen.net/hashgen/ | 43 | // crc64/we of string "CANCEL_ERROR" generated at http://www.nitrxgen.net/hashgen/ |
33 | static DECLARE_CONST_UNIQUE_KEY(CANCEL_ERROR, 0xe97d41626cc97577); // 'cancel_error' sentinel | 44 | static DECLARE_CONST_UNIQUE_KEY(CANCEL_ERROR, 0xe97d41626cc97577); // 'cancel_error' sentinel |
34 | 45 | ||
35 | // crc64/we of string "CANCEL_TEST_KEY" generated at http://www.nitrxgen.net/hashgen/ | 46 | // crc64/we of string "CANCEL_TEST_KEY" generated at http://www.nitrxgen.net/hashgen/ |
36 | static DECLARE_CONST_UNIQUE_KEY(CANCEL_TEST_KEY, 0xe66f5960c57d133a); // used as registry key | 47 | static DECLARE_CONST_UNIQUE_KEY(CANCEL_TEST_KEY, 0xe66f5960c57d133a); // used as registry key |
37 | 48 | ||
38 | void cancel_hook( lua_State* L, lua_Debug* ar); | 49 | cancel_result thread_cancel( lua_State* L, Lane* s, CancelOp op_, double secs_, bool_t force_, double waitkill_timeout_); |
39 | |||
40 | cancel_result thread_cancel( lua_State* L, Lane* s, double secs_, bool_t force_, double waitkill_timeout_); | ||
41 | 50 | ||
42 | static inline int cancel_error( lua_State* L) | 51 | static inline int cancel_error( lua_State* L) |
43 | { | 52 | { |
diff --git a/src/lanes.c b/src/lanes.c index cbac6da..8f159a9 100644 --- a/src/lanes.c +++ b/src/lanes.c | |||
@@ -452,7 +452,7 @@ static int selfdestruct_gc( lua_State* L) | |||
452 | while( s != SELFDESTRUCT_END) | 452 | while( s != SELFDESTRUCT_END) |
453 | { | 453 | { |
454 | // attempt a regular unforced hard cancel with a small timeout | 454 | // attempt a regular unforced hard cancel with a small timeout |
455 | bool_t cancelled = THREAD_ISNULL( s->thread) || thread_cancel( L, s, 0.0001, FALSE, 0.0); | 455 | bool_t cancelled = THREAD_ISNULL( s->thread) || thread_cancel( L, s, CO_Hard, 0.0001, FALSE, 0.0); |
456 | // if we failed, and we know the thread is waiting on a linda | 456 | // if we failed, and we know the thread is waiting on a linda |
457 | if( cancelled == FALSE && s->status == WAITING && s->waiting_on != NULL) | 457 | if( cancelled == FALSE && s->status == WAITING && s->waiting_on != NULL) |
458 | { | 458 | { |
@@ -1027,7 +1027,6 @@ static DECLARE_CONST_UNIQUE_KEY( GCCB_KEY, 0xcfb1f046ef074e88); | |||
1027 | //--- | 1027 | //--- |
1028 | // lane_ud = lane_new( function | 1028 | // lane_ud = lane_new( function |
1029 | // , [libs_str] | 1029 | // , [libs_str] |
1030 | // , [cancelstep_uint=0] | ||
1031 | // , [priority_int=0] | 1030 | // , [priority_int=0] |
1032 | // , [globals_tbl] | 1031 | // , [globals_tbl] |
1033 | // , [package_tbl] | 1032 | // , [package_tbl] |
@@ -1044,14 +1043,13 @@ LUAG_FUNC( lane_new) | |||
1044 | Lane** ud; | 1043 | Lane** ud; |
1045 | 1044 | ||
1046 | char const* libs_str = lua_tostring( L, 2); | 1045 | char const* libs_str = lua_tostring( L, 2); |
1047 | uint_t cancelstep_idx = luaG_optunsigned( L, 3, 0); | 1046 | int const priority = (int) luaL_optinteger( L, 3, 0); |
1048 | int const priority = (int) luaL_optinteger( L, 4, 0); | 1047 | uint_t globals_idx = lua_isnoneornil( L, 4) ? 0 : 4; |
1049 | uint_t globals_idx = lua_isnoneornil( L, 5) ? 0 : 5; | 1048 | uint_t package_idx = lua_isnoneornil( L, 5) ? 0 : 5; |
1050 | uint_t package_idx = lua_isnoneornil( L, 6) ? 0 : 6; | 1049 | uint_t required_idx = lua_isnoneornil( L, 6) ? 0 : 6; |
1051 | uint_t required_idx = lua_isnoneornil( L, 7) ? 0 : 7; | 1050 | uint_t gc_cb_idx = lua_isnoneornil( L, 7) ? 0 : 7; |
1052 | uint_t gc_cb_idx = lua_isnoneornil( L, 8) ? 0 : 8; | 1051 | |
1053 | 1052 | #define FIXED_ARGS 7 | |
1054 | #define FIXED_ARGS 8 | ||
1055 | int const nargs = lua_gettop(L) - FIXED_ARGS; | 1053 | int const nargs = lua_gettop(L) - FIXED_ARGS; |
1056 | Universe* U = universe_get( L); | 1054 | Universe* U = universe_get( L); |
1057 | ASSERT_L( nargs >= 0); | 1055 | ASSERT_L( nargs >= 0); |
@@ -1074,7 +1072,7 @@ LUAG_FUNC( lane_new) | |||
1074 | STACK_GROW( L2, nargs + 3); // | 1072 | STACK_GROW( L2, nargs + 3); // |
1075 | STACK_CHECK( L2, 0); | 1073 | STACK_CHECK( L2, 0); |
1076 | 1074 | ||
1077 | STACK_GROW( L, 3); // func libs cancelstep priority globals package required gc_cb [... args ...] | 1075 | STACK_GROW( L, 3); // func libs priority globals package required gc_cb [... args ...] |
1078 | STACK_CHECK( L, 0); | 1076 | STACK_CHECK( L, 0); |
1079 | 1077 | ||
1080 | // give a default "Lua" name to the thread to see VM name in Decoda debugger | 1078 | // give a default "Lua" name to the thread to see VM name in Decoda debugger |
@@ -1103,8 +1101,8 @@ LUAG_FUNC( lane_new) | |||
1103 | return luaL_error( L, "expected required module list as a table, got %s", luaL_typename( L, required_idx)); | 1101 | return luaL_error( L, "expected required module list as a table, got %s", luaL_typename( L, required_idx)); |
1104 | } | 1102 | } |
1105 | 1103 | ||
1106 | lua_pushnil( L); // func libs cancelstep priority globals package required gc_cb [... args ...] nil | 1104 | lua_pushnil( L); // func libs priority globals package required gc_cb [... args ...] nil |
1107 | while( lua_next( L, required_idx) != 0) // func libs cancelstep priority globals package required gc_cb [... args ...] n "modname" | 1105 | while( lua_next( L, required_idx) != 0) // func libs priority globals package required gc_cb [... args ...] n "modname" |
1108 | { | 1106 | { |
1109 | if( lua_type( L, -1) != LUA_TSTRING || lua_type( L, -2) != LUA_TNUMBER || lua_tonumber( L, -2) != nbRequired) | 1107 | if( lua_type( L, -1) != LUA_TSTRING || lua_type( L, -2) != LUA_TNUMBER || lua_tonumber( L, -2) != nbRequired) |
1110 | { | 1108 | { |
@@ -1130,7 +1128,7 @@ LUAG_FUNC( lane_new) | |||
1130 | if( lua_pcall( L2, 1, 1, 0) != LUA_OK) // ret/errcode | 1128 | if( lua_pcall( L2, 1, 1, 0) != LUA_OK) // ret/errcode |
1131 | { | 1129 | { |
1132 | // propagate error to main state if any | 1130 | // propagate error to main state if any |
1133 | luaG_inter_move( U, L2, L, 1, eLM_LaneBody); // func libs cancelstep priority globals package required gc_cb [... args ...] n "modname" error | 1131 | luaG_inter_move( U, L2, L, 1, eLM_LaneBody); // func libs priority globals package required gc_cb [... args ...] n "modname" error |
1134 | return lua_error( L); | 1132 | return lua_error( L); |
1135 | } | 1133 | } |
1136 | // after requiring the module, register the functions it exported in our name<->function database | 1134 | // after requiring the module, register the functions it exported in our name<->function database |
@@ -1138,9 +1136,9 @@ LUAG_FUNC( lane_new) | |||
1138 | lua_pop( L2, 1); // | 1136 | lua_pop( L2, 1); // |
1139 | } | 1137 | } |
1140 | } | 1138 | } |
1141 | lua_pop( L, 1); // func libs cancelstep priority globals package required gc_cb [... args ...] n | 1139 | lua_pop( L, 1); // func libs priority globals package required gc_cb [... args ...] n |
1142 | ++ nbRequired; | 1140 | ++ nbRequired; |
1143 | } // func libs cancelstep priority globals package required gc_cb [... args ...] | 1141 | } // func libs priority globals package required gc_cb [... args ...] |
1144 | DEBUGSPEW_CODE( -- U->debugspew_indent_depth); | 1142 | DEBUGSPEW_CODE( -- U->debugspew_indent_depth); |
1145 | } | 1143 | } |
1146 | STACK_MID( L, 0); | 1144 | STACK_MID( L, 0); |
@@ -1158,16 +1156,16 @@ LUAG_FUNC( lane_new) | |||
1158 | } | 1156 | } |
1159 | 1157 | ||
1160 | DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); | 1158 | DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); |
1161 | lua_pushnil( L); // func libs cancelstep priority globals package required gc_cb [... args ...] nil | 1159 | lua_pushnil( L); // func libs priority globals package required gc_cb [... args ...] nil |
1162 | // Lua 5.2 wants us to push the globals table on the stack | 1160 | // Lua 5.2 wants us to push the globals table on the stack |
1163 | lua_pushglobaltable( L2); // _G | 1161 | lua_pushglobaltable( L2); // _G |
1164 | while( lua_next( L, globals_idx)) // func libs cancelstep priority globals package required gc_cb [... args ...] k v | 1162 | while( lua_next( L, globals_idx)) // func libs priority globals package required gc_cb [... args ...] k v |
1165 | { | 1163 | { |
1166 | luaG_inter_copy( U, L, L2, 2, eLM_LaneBody); // _G k v | 1164 | luaG_inter_copy( U, L, L2, 2, eLM_LaneBody); // _G k v |
1167 | // assign it in L2's globals table | 1165 | // assign it in L2's globals table |
1168 | lua_rawset( L2, -3); // _G | 1166 | lua_rawset( L2, -3); // _G |
1169 | lua_pop( L, 1); // func libs cancelstep priority globals package required gc_cb [... args ...] k | 1167 | lua_pop( L, 1); // func libs priority globals package required gc_cb [... args ...] k |
1170 | } // func libs cancelstep priority globals package required gc_cb [... args ...] | 1168 | } // func libs priority globals package required gc_cb [... args ...] |
1171 | lua_pop( L2, 1); // | 1169 | lua_pop( L2, 1); // |
1172 | 1170 | ||
1173 | DEBUGSPEW_CODE( -- U->debugspew_indent_depth); | 1171 | DEBUGSPEW_CODE( -- U->debugspew_indent_depth); |
@@ -1181,8 +1179,8 @@ LUAG_FUNC( lane_new) | |||
1181 | int res; | 1179 | int res; |
1182 | DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "lane_new: transfer lane body\n" INDENT_END)); | 1180 | DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "lane_new: transfer lane body\n" INDENT_END)); |
1183 | DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); | 1181 | DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); |
1184 | lua_pushvalue( L, 1); // func libs cancelstep priority globals package required gc_cb [... args ...] func | 1182 | lua_pushvalue( L, 1); // func libs priority globals package required gc_cb [... args ...] func |
1185 | res = luaG_inter_move( U, L, L2, 1, eLM_LaneBody); // func libs cancelstep priority globals package required gc_cb [... args ...] // func | 1183 | res = luaG_inter_move( U, L, L2, 1, eLM_LaneBody); // func libs priority globals package required gc_cb [... args ...] // func |
1186 | DEBUGSPEW_CODE( -- U->debugspew_indent_depth); | 1184 | DEBUGSPEW_CODE( -- U->debugspew_indent_depth); |
1187 | if( res != 0) | 1185 | if( res != 0) |
1188 | { | 1186 | { |
@@ -1207,7 +1205,7 @@ LUAG_FUNC( lane_new) | |||
1207 | int res; | 1205 | int res; |
1208 | DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "lane_new: transfer lane arguments\n" INDENT_END)); | 1206 | DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "lane_new: transfer lane arguments\n" INDENT_END)); |
1209 | DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); | 1207 | DEBUGSPEW_CODE( ++ U->debugspew_indent_depth); |
1210 | res = luaG_inter_move( U, L, L2, nargs, eLM_LaneBody); // func libs cancelstep priority globals package required gc_cb // func [... args ...] | 1208 | res = luaG_inter_move( U, L, L2, nargs, eLM_LaneBody); // func libs priority globals package required gc_cb // func [... args ...] |
1211 | DEBUGSPEW_CODE( -- U->debugspew_indent_depth); | 1209 | DEBUGSPEW_CODE( -- U->debugspew_indent_depth); |
1212 | if( res != 0) | 1210 | if( res != 0) |
1213 | { | 1211 | { |
@@ -1222,7 +1220,7 @@ LUAG_FUNC( lane_new) | |||
1222 | // 's' is allocated from heap, not Lua, since its life span may surpass the handle's (if free running thread) | 1220 | // 's' is allocated from heap, not Lua, since its life span may surpass the handle's (if free running thread) |
1223 | // | 1221 | // |
1224 | // a Lane full userdata needs a single uservalue | 1222 | // a Lane full userdata needs a single uservalue |
1225 | ud = lua_newuserdatauv( L, sizeof( Lane*), 1); // func libs cancelstep priority globals package required gc_cb lane | 1223 | ud = lua_newuserdatauv( L, sizeof( Lane*), 1); // func libs priority globals package required gc_cb lane |
1226 | s = *ud = (Lane*) malloc( sizeof( Lane)); | 1224 | s = *ud = (Lane*) malloc( sizeof( Lane)); |
1227 | if( s == NULL) | 1225 | if( s == NULL) |
1228 | { | 1226 | { |
@@ -1252,8 +1250,8 @@ LUAG_FUNC( lane_new) | |||
1252 | 1250 | ||
1253 | // Set metatable for the userdata | 1251 | // Set metatable for the userdata |
1254 | // | 1252 | // |
1255 | lua_pushvalue( L, lua_upvalueindex( 1)); // func libs cancelstep priority globals package required gc_cb lane mt | 1253 | lua_pushvalue( L, lua_upvalueindex( 1)); // func libs priority globals package required gc_cb lane mt |
1256 | lua_setmetatable( L, -2); // func libs cancelstep priority globals package required gc_cb lane | 1254 | lua_setmetatable( L, -2); // func libs priority globals package required gc_cb lane |
1257 | STACK_MID( L, 1); | 1255 | STACK_MID( L, 1); |
1258 | 1256 | ||
1259 | // Create uservalue for the userdata | 1257 | // Create uservalue for the userdata |
@@ -1263,21 +1261,16 @@ LUAG_FUNC( lane_new) | |||
1263 | // Store the gc_cb callback in the uservalue | 1261 | // Store the gc_cb callback in the uservalue |
1264 | if( gc_cb_idx > 0) | 1262 | if( gc_cb_idx > 0) |
1265 | { | 1263 | { |
1266 | push_unique_key( L, GCCB_KEY); // func libs cancelstep priority globals package required gc_cb lane uv k | 1264 | push_unique_key( L, GCCB_KEY); // func libs priority globals package required gc_cb lane uv k |
1267 | lua_pushvalue( L, gc_cb_idx); // func libs cancelstep priority globals package required gc_cb lane uv k gc_cb | 1265 | lua_pushvalue( L, gc_cb_idx); // func libs priority globals package required gc_cb lane uv k gc_cb |
1268 | lua_rawset( L, -3); // func libs cancelstep priority globals package required gc_cb lane uv | 1266 | lua_rawset( L, -3); // func libs priority globals package required gc_cb lane uv |
1269 | } | 1267 | } |
1270 | 1268 | ||
1271 | lua_setiuservalue( L, -2, 1); // func libs cancelstep priority globals package required gc_cb lane | 1269 | lua_setiuservalue( L, -2, 1); // func libs priority globals package required gc_cb lane |
1272 | 1270 | ||
1273 | // Store 's' in the lane's registry, for 'cancel_test()' (even if 'cs'==0 we still do cancel tests at pending send/receive). | 1271 | // Store 's' in the lane's registry, for 'cancel_test()' (we do cancel tests at pending send/receive). |
1274 | REGISTRY_SET( L2, CANCEL_TEST_KEY, lua_pushlightuserdata( L2, s)); // func [... args ...] | 1272 | REGISTRY_SET( L2, CANCEL_TEST_KEY, lua_pushlightuserdata( L2, s)); // func [... args ...] |
1275 | 1273 | ||
1276 | if( cancelstep_idx) | ||
1277 | { | ||
1278 | lua_sethook( L2, cancel_hook, LUA_MASKCOUNT, cancelstep_idx); | ||
1279 | } | ||
1280 | |||
1281 | STACK_END( L, 1); | 1274 | STACK_END( L, 1); |
1282 | STACK_END( L2, 1 + nargs); | 1275 | STACK_END( L2, 1 + nargs); |
1283 | 1276 | ||
@@ -1402,7 +1395,7 @@ static char const * thread_status_string( Lane* s) | |||
1402 | return str; | 1395 | return str; |
1403 | } | 1396 | } |
1404 | 1397 | ||
1405 | static int push_thread_status( lua_State* L, Lane* s) | 1398 | int push_thread_status( lua_State* L, Lane* s) |
1406 | { | 1399 | { |
1407 | char const* const str = thread_status_string( s); | 1400 | char const* const str = thread_status_string( s); |
1408 | ASSERT_L( str); | 1401 | ASSERT_L( str); |
diff --git a/src/lanes.h b/src/lanes.h index de60d6d..da0dd26 100644 --- a/src/lanes.h +++ b/src/lanes.h | |||
@@ -11,7 +11,7 @@ | |||
11 | #endif // (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) | 11 | #endif // (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) |
12 | 12 | ||
13 | #define LANES_VERSION_MAJOR 3 | 13 | #define LANES_VERSION_MAJOR 3 |
14 | #define LANES_VERSION_MINOR 13 | 14 | #define LANES_VERSION_MINOR 14 |
15 | #define LANES_VERSION_PATCH 0 | 15 | #define LANES_VERSION_PATCH 0 |
16 | 16 | ||
17 | #define LANES_MIN_VERSION_REQUIRED(MAJOR, MINOR, PATCH) ((LANES_VERSION_MAJOR>MAJOR) || (LANES_VERSION_MAJOR==MAJOR && (LANES_VERSION_MINOR>MINOR || (LANES_VERSION_MINOR==MINOR && LANES_VERSION_PATCH>=PATCH)))) | 17 | #define LANES_MIN_VERSION_REQUIRED(MAJOR, MINOR, PATCH) ((LANES_VERSION_MAJOR>MAJOR) || (LANES_VERSION_MAJOR==MAJOR && (LANES_VERSION_MINOR>MINOR || (LANES_VERSION_MINOR==MINOR && LANES_VERSION_PATCH>=PATCH)))) |
diff --git a/src/lanes.lua b/src/lanes.lua index ba9da81..4d6deac 100644 --- a/src/lanes.lua +++ b/src/lanes.lua | |||
@@ -146,7 +146,7 @@ lanes.configure = function( settings_) | |||
146 | author= "Asko Kauppi <akauppi@gmail.com>, Benoit Germain <bnt.germain@gmail.com>", | 146 | author= "Asko Kauppi <akauppi@gmail.com>, Benoit Germain <bnt.germain@gmail.com>", |
147 | description= "Running multiple Lua states in parallel", | 147 | description= "Running multiple Lua states in parallel", |
148 | license= "MIT/X11", | 148 | license= "MIT/X11", |
149 | copyright= "Copyright (c) 2007-10, Asko Kauppi; (c) 2011-18, Benoit Germain", | 149 | copyright= "Copyright (c) 2007-10, Asko Kauppi; (c) 2011-19, Benoit Germain", |
150 | version = assert( core.version) | 150 | version = assert( core.version) |
151 | } | 151 | } |
152 | 152 | ||
@@ -198,13 +198,6 @@ lanes.configure = function( settings_) | |||
198 | -- | 198 | -- |
199 | -- 'opt': .priority: int (-3..+3) smaller is lower priority (0 = default) | 199 | -- 'opt': .priority: int (-3..+3) smaller is lower priority (0 = default) |
200 | -- | 200 | -- |
201 | -- .cancelstep: bool | uint | ||
202 | -- false: cancellation check only at pending Linda operations | ||
203 | -- (send/receive) so no runtime performance penalty (default) | ||
204 | -- true: adequate cancellation check (same as 100) | ||
205 | -- >0: cancellation check every x Lua lines (small number= faster | ||
206 | -- reaction but more performance overhead) | ||
207 | -- | ||
208 | -- .globals: table of globals to set for a new thread (passed by value) | 201 | -- .globals: table of globals to set for a new thread (passed by value) |
209 | -- | 202 | -- |
210 | -- .required: table of packages to require | 203 | -- .required: table of packages to require |
@@ -246,10 +239,6 @@ lanes.configure = function( settings_) | |||
246 | local tv = type( v_) | 239 | local tv = type( v_) |
247 | return (tv == "number") and v_ or raise_option_error( "priority", tv, v_) | 240 | return (tv == "number") and v_ or raise_option_error( "priority", tv, v_) |
248 | end, | 241 | end, |
249 | cancelstep = function( v_) | ||
250 | local tv = type( v_) | ||
251 | return (tv == "number") and v_ or (v_ == true) and 100 or (v_ == false) and 0 or raise_option_error( "cancelstep", tv, v_) | ||
252 | end, | ||
253 | globals = function( v_) | 242 | globals = function( v_) |
254 | local tv = type( v_) | 243 | local tv = type( v_) |
255 | return (tv == "table") and v_ or raise_option_error( "globals", tv, v_) | 244 | return (tv == "table") and v_ or raise_option_error( "globals", tv, v_) |
@@ -334,10 +323,10 @@ lanes.configure = function( settings_) | |||
334 | end | 323 | end |
335 | end | 324 | end |
336 | 325 | ||
337 | local cancelstep, priority, globals, package, required, gc_cb = opt.cancelstep, opt.priority, opt.globals, opt.package or package, opt.required, opt.gc_cb | 326 | local priority, globals, package, required, gc_cb = opt.priority, opt.globals, opt.package or package, opt.required, opt.gc_cb |
338 | return function( ...) | 327 | return function( ...) |
339 | -- must pass functions args last else they will be truncated to the first one | 328 | -- must pass functions args last else they will be truncated to the first one |
340 | return core_lane_new( func, libs, cancelstep, priority, globals, package, required, gc_cb, ...) | 329 | return core_lane_new( func, libs, priority, globals, package, required, gc_cb, ...) |
341 | end | 330 | end |
342 | end -- gen() | 331 | end -- gen() |
343 | 332 | ||
diff --git a/src/lanes_private.h b/src/lanes_private.h index ac05129..1a15969 100644 --- a/src/lanes_private.h +++ b/src/lanes_private.h | |||
@@ -89,4 +89,7 @@ static inline Lane* get_lane_from_registry( lua_State* L) | |||
89 | return s; | 89 | return s; |
90 | } | 90 | } |
91 | 91 | ||
92 | int push_thread_status( lua_State* L, Lane* s); | ||
93 | |||
94 | |||
92 | #endif // __lanes_private_h__ \ No newline at end of file | 95 | #endif // __lanes_private_h__ \ No newline at end of file |
diff --git a/tests/basic.lua b/tests/basic.lua index 020fe78..6fcfd53 100644 --- a/tests/basic.lua +++ b/tests/basic.lua | |||
@@ -114,7 +114,7 @@ collectgarbage() | |||
114 | 114 | ||
115 | PRINT( "\n\n", "---=== Tasking (cancelling) ===---", "\n\n") | 115 | PRINT( "\n\n", "---=== Tasking (cancelling) ===---", "\n\n") |
116 | 116 | ||
117 | local task_launch2= lanes_gen( "", { cancelstep=100, globals={hey=true}, gc_cb = gc_cb}, task ) | 117 | local task_launch2= lanes_gen( "", { globals={hey=true}, gc_cb = gc_cb}, task ) |
118 | 118 | ||
119 | local N=999999999 | 119 | local N=999999999 |
120 | local lane9= task_launch2(1,N,1) -- huuuuuuge... | 120 | local lane9= task_launch2(1,N,1) -- huuuuuuge... |
@@ -138,7 +138,7 @@ if st=="done" then | |||
138 | end | 138 | end |
139 | assert( st=="running" ) | 139 | assert( st=="running" ) |
140 | 140 | ||
141 | lane9:cancel() | 141 | lane9:cancel( "count", 100) -- 0 timeout, 100 instructions count hook |
142 | 142 | ||
143 | local t0= os.time() | 143 | local t0= os.time() |
144 | while os.time()-t0 < 5 do | 144 | while os.time()-t0 < 5 do |
@@ -166,7 +166,7 @@ end | |||
166 | local wait_send_lane = lanes.gen( "*", wait_send)() | 166 | local wait_send_lane = lanes.gen( "*", wait_send)() |
167 | repeat until wait_send_lane.status == "waiting" | 167 | repeat until wait_send_lane.status == "waiting" |
168 | print "wait_send_lane is waiting" | 168 | print "wait_send_lane is waiting" |
169 | wait_send_lane:cancel() | 169 | wait_send_lane:cancel() -- hard cancel, 0 timeout |
170 | repeat until wait_send_lane.status == "cancelled" | 170 | repeat until wait_send_lane.status == "cancelled" |
171 | print "wait_send_lane is cancelled" | 171 | print "wait_send_lane is cancelled" |
172 | --################################################]] | 172 | --################################################]] |
@@ -179,7 +179,7 @@ end | |||
179 | local wait_receive_lane = lanes.gen( "*", wait_receive)() | 179 | local wait_receive_lane = lanes.gen( "*", wait_receive)() |
180 | repeat until wait_receive_lane.status == "waiting" | 180 | repeat until wait_receive_lane.status == "waiting" |
181 | print "wait_receive_lane is waiting" | 181 | print "wait_receive_lane is waiting" |
182 | wait_receive_lane:cancel() | 182 | wait_receive_lane:cancel() -- hard cancel, 0 timeout |
183 | repeat until wait_receive_lane.status == "cancelled" | 183 | repeat until wait_receive_lane.status == "cancelled" |
184 | print "wait_receive_lane is cancelled" | 184 | print "wait_receive_lane is cancelled" |
185 | --################################################]] | 185 | --################################################]] |
@@ -192,7 +192,7 @@ end | |||
192 | local wait_receive_batched_lane = lanes.gen( "*", wait_receive_batched)() | 192 | local wait_receive_batched_lane = lanes.gen( "*", wait_receive_batched)() |
193 | repeat until wait_receive_batched_lane.status == "waiting" | 193 | repeat until wait_receive_batched_lane.status == "waiting" |
194 | print "wait_receive_batched_lane is waiting" | 194 | print "wait_receive_batched_lane is waiting" |
195 | wait_receive_batched_lane:cancel() | 195 | wait_receive_batched_lane:cancel() -- hard cancel, 0 timeout |
196 | repeat until wait_receive_batched_lane.status == "cancelled" | 196 | repeat until wait_receive_batched_lane.status == "cancelled" |
197 | print "wait_receive_batched_lane is cancelled" | 197 | print "wait_receive_batched_lane is cancelled" |
198 | --################################################]] | 198 | --################################################]] |
diff --git a/tests/cancel.lua b/tests/cancel.lua index 6429487..0d9d143 100644 --- a/tests/cancel.lua +++ b/tests/cancel.lua | |||
@@ -27,9 +27,29 @@ linda:set( "lock") | |||
27 | linda:limit( "atomic", -1) | 27 | linda:limit( "atomic", -1) |
28 | linda:set( "atomic") | 28 | linda:set( "atomic") |
29 | 29 | ||
30 | -- a numeric value to read | ||
31 | linda:set( "val", 33.0) | ||
32 | |||
33 | print "test OK" | ||
30 | --#################################################################### | 34 | --#################################################################### |
31 | 35 | ||
32 | local laneBody = function( timeout_) | 36 | local waitCancellation = function( h, expected_status) |
37 | local l = lanes.linda() | ||
38 | if expected_status ~= "running" then | ||
39 | repeat | ||
40 | -- print( "lane status:", h.status) | ||
41 | l:receive( 0.1, "yeah") -- wait a bit | ||
42 | until h.status ~= "running" | ||
43 | end | ||
44 | print( "lane status:", h.status) | ||
45 | assert( h.status == expected_status, h.status .. " ~= " .. expected_status) | ||
46 | print "test OK" | ||
47 | end | ||
48 | |||
49 | local laneBody = function( mode_, payload_) | ||
50 | local name = "laneBody("..tostring(mode_)..","..tostring(payload_)..")" | ||
51 | set_debug_threadname( name) | ||
52 | |||
33 | set_finalizer( function( err, stk) | 53 | set_finalizer( function( err, stk) |
34 | if err == lanes.cancel_error then | 54 | if err == lanes.cancel_error then |
35 | -- note that we don't get the cancel_error when running wrapped inside a protected call if it doesn't rethrow it | 55 | -- note that we don't get the cancel_error when running wrapped inside a protected call if it doesn't rethrow it |
@@ -41,14 +61,39 @@ local laneBody = function( timeout_) | |||
41 | end | 61 | end |
42 | end) | 62 | end) |
43 | 63 | ||
44 | print( " entering lane with " .. tostring( timeout_) .. " timeout") | 64 | print( " entering " , name) |
45 | repeat | 65 | repeat |
46 | -- block-wait to be hard-cancelled | 66 | if mode_ == "receive" then |
47 | print " lane calling receive()" | 67 | -- linda mode |
48 | local key, val = linda:receive( timeout_, "boob") | 68 | io.stdout:write( " lane calling receive() ... ") |
49 | print( " receive() -> ", lanes.cancel_error == key and "cancel_error" or tostring( key), tostring( val)) | 69 | local key, val = linda:receive( payload_, "boob") |
70 | print( lanes.cancel_error == key and "cancel_error" or tostring( key), tostring( val)) | ||
71 | if key == lanes.cancel_error then | ||
72 | break -- gracefully abort loop | ||
73 | end | ||
74 | elseif mode_ == "get" then | ||
75 | -- busy wait mode getting data from the linda | ||
76 | io.stdout:write( " lane busy waiting ... ") | ||
77 | for i = 1, payload_ do | ||
78 | -- force a non-jitable call | ||
79 | local a = linda:get( "val") | ||
80 | a = a * 2 | ||
81 | end | ||
82 | print( "again?") | ||
83 | elseif mode_ == "busy" then | ||
84 | -- busy wait mode in pure Lua code | ||
85 | io.stdout:write( " lane busy waiting ... ") | ||
86 | local a = linda:get( "val") | ||
87 | for i = 1, payload_ do | ||
88 | a = a * 2 | ||
89 | a = math.sin( a) * math.sin( a) + math.cos( a) * math.cos( a) -- aka 1 | ||
90 | end | ||
91 | print( "again?") | ||
92 | else | ||
93 | error "no mode: raise an error" | ||
94 | end | ||
50 | until cancel_test() -- soft cancel self test | 95 | until cancel_test() -- soft cancel self test |
51 | print " shutting down after breaking out of loop" | 96 | print " lane shutting down after breaking out of loop" |
52 | end | 97 | end |
53 | 98 | ||
54 | local protectedBody = function( ...) | 99 | local protectedBody = function( ...) |
@@ -61,7 +106,8 @@ local protectedBody = function( ...) | |||
61 | -- Lua 5.1 doesn't pass additional xpcall arguments to the called function | 106 | -- Lua 5.1 doesn't pass additional xpcall arguments to the called function |
62 | -- therefore we need to create a closure that has no arguments but pulls everything from its upvalue | 107 | -- therefore we need to create a closure that has no arguments but pulls everything from its upvalue |
63 | local params = {...} | 108 | local params = {...} |
64 | local paramLessClosure = function() laneBody(table.unpack( params)) end | 109 | local unpack = table.unpack or unpack -- unpack for 5.1, table.unpack for 5.2+ |
110 | local paramLessClosure = function() laneBody(unpack( params)) end | ||
65 | local status, message = xpcall( paramLessClosure, errorHandler) | 111 | local status, message = xpcall( paramLessClosure, errorHandler) |
66 | if status == false then | 112 | if status == false then |
67 | print( " error handler rethrowing '" .. (ce == message and "cancel_error"or tostring( message)) .. "'") | 113 | print( " error handler rethrowing '" .. (ce == message and "cancel_error"or tostring( message)) .. "'") |
@@ -71,66 +117,92 @@ local protectedBody = function( ...) | |||
71 | end | 117 | end |
72 | 118 | ||
73 | --#################################################################### | 119 | --#################################################################### |
120 | --#################################################################### | ||
121 | |||
122 | print "\n\n####################################################################\nbegin linda cancel test\n" | ||
123 | h = lanes.gen( "*", laneBody)( "receive", nil) -- start an infinite wait on the linda | ||
124 | |||
125 | print "wait 1s" | ||
126 | linda:receive( 1, "yeah") | ||
127 | |||
128 | -- linda cancel: linda:receive() returns cancel_error immediately | ||
129 | linda:cancel( "both") | ||
130 | |||
131 | -- wait until cancellation is effective. | ||
132 | waitCancellation( h, "done") | ||
133 | |||
134 | -- reset the linda so that the other tests work | ||
135 | linda:cancel( "none") | ||
74 | 136 | ||
75 | print "####################################################################\nbegin soft cancel test\n" | 137 | print "\n\n####################################################################\nbegin soft cancel test\n" |
76 | h = lanes.gen("*", protectedBody)( 0.666) | 138 | h = lanes.gen( "*", protectedBody)( "receive") -- start an infinite wait on the linda |
77 | print "wait 3s" | ||
78 | linda:receive( 3, "yeah") | ||
79 | 139 | ||
80 | -- soft cancel | 140 | print "wait 1s" |
81 | print "soft cancel with awakening" | 141 | linda:receive( 1, "yeah") |
82 | h:cancel( -1, true) | ||
83 | 142 | ||
84 | -- wait 10s: the lane will interrupt its loop and print the exit message | 143 | -- soft cancel, no awakening of waiting linda operations, should timeout |
144 | local a, b = h:cancel( "soft", 1, false) | ||
145 | -- cancellation should fail as the lane is still waiting on its linda | ||
146 | assert( a == false and b == "timeout") | ||
147 | waitCancellation( h, "waiting") | ||
148 | |||
149 | -- soft cancel, this time awakens waiting linda operations, which returns cancel_error immediately, no timeout. | ||
150 | h:cancel( "soft", true) | ||
151 | |||
152 | -- wait until cancellation is effective. the lane will interrupt its loop and print the exit message | ||
153 | waitCancellation( h, "done") | ||
154 | |||
155 | -- do return end | ||
156 | |||
157 | print "\n\n####################################################################\nbegin hook cancel test\n" | ||
158 | h = lanes.gen( "*", protectedBody)( "get", 300000) | ||
85 | print "wait 2s" | 159 | print "wait 2s" |
86 | linda:receive( 2, "yeah") | 160 | linda:receive( 2, "yeah") |
87 | 161 | ||
88 | --#################################################################### | 162 | -- count hook cancel after 3 instructions |
163 | h:cancel( "count", 300, 5.0) | ||
164 | |||
165 | -- wait until cancellation is effective. the lane will interrupt its loop and print the exit message | ||
166 | waitCancellation( h, "cancelled") | ||
89 | 167 | ||
90 | print "\n\n####################################################################\nbegin hard cancel test\n" | 168 | print "\n\n####################################################################\nbegin hard cancel test\n" |
91 | h = lanes.gen("*", protectedBody)() | 169 | h = lanes.gen( "*", protectedBody)( "receive", nil) -- infinite timeout |
92 | 170 | ||
93 | -- wait 3s before cancelling the lane | 171 | -- wait 2s before cancelling the lane |
94 | print "wait 3s" | 172 | print "wait 2s" |
95 | linda:receive( 3, "yeah") | 173 | linda:receive( 2, "yeah") |
96 | 174 | ||
97 | -- hard cancel and wait 10s: the lane will be interrupted from inside its current linda:receive() and won't return from it | 175 | -- hard cancel: the lane will be interrupted from inside its current linda:receive() and won't return from it |
98 | print "hard cancel (always awakens)" | ||
99 | h:cancel() | 176 | h:cancel() |
100 | 177 | ||
101 | print "wait 5s" | 178 | -- wait until cancellation is effective. the lane will be stopped by the linda operation throwing an error |
102 | linda:receive( 5, "yeah") | 179 | waitCancellation( h, "cancelled") |
103 | |||
104 | --#################################################################### | ||
105 | 180 | ||
106 | print "\n\n####################################################################\nbegin hard cancel test with unprotected lane body\n" | 181 | print "\n\n####################################################################\nbegin hard cancel test with unprotected lane body\n" |
107 | h = lanes.gen("*", laneBody)() | 182 | h = lanes.gen( "*", laneBody)( "receive", nil) |
108 | 183 | ||
109 | -- wait 3s before cancelling the lane | 184 | -- wait 2s before cancelling the lane |
110 | print "wait 3s" | 185 | print "wait 2s" |
111 | linda:receive( 3, "yeah") | 186 | linda:receive( 2, "yeah") |
112 | 187 | ||
113 | -- hard cancel: the lane will be interrupted from inside its current linda:receive() and won't return from it | 188 | -- hard cancel: the lane will be interrupted from inside its current linda:receive() and won't return from it |
114 | print "hard cancel (always awakens)" | ||
115 | h:cancel() | 189 | h:cancel() |
116 | 190 | ||
117 | print "wait 5s" | 191 | -- wait until cancellation is effective. the lane will be stopped by the linda operation throwing an error |
118 | linda:receive( 5, "yeah") | 192 | waitCancellation( h, "cancelled") |
119 | 193 | ||
120 | --#################################################################### | 194 | print "\n\n####################################################################\nbegin kill cancel test\n" |
121 | print "\n\n####################################################################\nbegin linda cancel test\n" | 195 | h = lanes.gen( "*", laneBody)( "busy", 50000000) -- start a pure Lua busy loop lane |
122 | h = lanes.gen("*", laneBody)() | ||
123 | 196 | ||
124 | -- wait 3s before cancelling the lane | 197 | -- wait 1/3s before cancelling the lane, before the busy loop can finish |
125 | print "wait 3s" | 198 | print "wait 0.3s" |
126 | linda:receive( 3, "yeah") | 199 | linda:receive( 0.3, "yeah") |
127 | 200 | ||
128 | -- linda cancel: the lane will be interrupted from inside its current linda:receive() and won't return from it | 201 | -- hard cancel with kill: the lane thread will be forcefully terminated |
129 | print "linda cancel (always awakens the lane)" | 202 | h:cancel( true) |
130 | linda:cancel( "both") | ||
131 | 203 | ||
132 | print "wait 5s" | 204 | -- wait until cancellation is effective. the lane will be stopped by the linda operation throwing an error |
133 | linda:receive( 5, "yeah") | 205 | waitCancellation( h, "killed") |
134 | 206 | ||
135 | --#################################################################### | 207 | --#################################################################### |
136 | 208 | ||
diff --git a/tests/fifo.lua b/tests/fifo.lua index 47db4c9..bef60d5 100644 --- a/tests/fifo.lua +++ b/tests/fifo.lua | |||
@@ -6,11 +6,11 @@ | |||
6 | 6 | ||
7 | local lanes = require "lanes".configure{shutdown_timeout=3,with_timers=true} | 7 | local lanes = require "lanes".configure{shutdown_timeout=3,with_timers=true} |
8 | 8 | ||
9 | local linda= lanes.linda( "atom") | 9 | local linda = lanes.linda( "atom") |
10 | local atomic_inc= lanes.genatomic( linda, "FIFO_n" ) | 10 | local atomic_inc= lanes.genatomic( linda, "FIFO_n") |
11 | 11 | ||
12 | assert( atomic_inc()==1 ) | 12 | assert( atomic_inc()==1) |
13 | assert( atomic_inc()==2 ) | 13 | assert( atomic_inc()==2) |
14 | 14 | ||
15 | local function FIFO() | 15 | local function FIFO() |
16 | local my_channel= "FIFO"..atomic_inc() | 16 | local my_channel= "FIFO"..atomic_inc() |
@@ -18,32 +18,32 @@ local function FIFO() | |||
18 | return { | 18 | return { |
19 | -- Giving explicit 'nil' timeout allows numbers to be used as 'my_channel' | 19 | -- Giving explicit 'nil' timeout allows numbers to be used as 'my_channel' |
20 | -- | 20 | -- |
21 | send= function(self, ...) | 21 | send = function(self, ...) |
22 | linda:send( nil, my_channel, ... ) | 22 | linda:send( nil, my_channel, ...) |
23 | end, | 23 | end, |
24 | receive = function(self, timeout) | 24 | receive = function(self, timeout) |
25 | return linda:receive( timeout, my_channel ) | 25 | return linda:receive( timeout, my_channel) |
26 | end | 26 | end |
27 | } | 27 | } |
28 | end | 28 | end |
29 | 29 | ||
30 | local A= FIFO() | 30 | local A = FIFO() |
31 | local B= FIFO() | 31 | local B = FIFO() |
32 | 32 | ||
33 | print "Sending to A.." | 33 | print "Sending to A.." |
34 | A:send( 1,2,3,4,5 ) | 34 | A:send( 1,2,3,4,5) |
35 | 35 | ||
36 | print "Sending to B.." | 36 | print "Sending to B.." |
37 | B:send( 'a','b','c' ) | 37 | B:send( 'a','b','c') |
38 | 38 | ||
39 | print "Reading A.." | 39 | print "Reading A.." |
40 | print( A:receive( 1.0 ) ) | 40 | print( A:receive( 1.0)) |
41 | 41 | ||
42 | print "Reading B.." | 42 | print "Reading B.." |
43 | print( B:receive( 2.0 ) ) | 43 | print( B:receive( 2.0)) |
44 | 44 | ||
45 | -- Note: A and B can be passed between threads, or used as upvalues | 45 | -- Note: A and B can be passed between threads, or used as upvalues |
46 | -- by multiple threads (other parts will be copied but the 'linda' | 46 | -- by multiple threads (other parts will be copied but the 'linda' |
47 | -- handle is shared userdata and will thus point to the single place) | 47 | -- handle is shared userdata and will thus point to the single place) |
48 | lanes.timer_lane:cancel() | 48 | lanes.timer_lane:cancel() -- hard cancel, 0 timeout |
49 | lanes.timer_lane:join() \ No newline at end of file | 49 | lanes.timer_lane:join() \ No newline at end of file |
diff --git a/tests/timer.lua b/tests/timer.lua index 805d85c..ec23cee 100644 --- a/tests/timer.lua +++ b/tests/timer.lua | |||
@@ -100,5 +100,5 @@ PRINT "...making sure no ticks are coming..." | |||
100 | local k,v= linda:receive( 10, T1,T2 ) -- should not get any | 100 | local k,v= linda:receive( 10, T1,T2 ) -- should not get any |
101 | assert(v==nil) | 101 | assert(v==nil) |
102 | 102 | ||
103 | lanes.timer_lane:cancel() | 103 | lanes.timer_lane:cancel() -- hard cancel, 0 timeout |
104 | print (lanes.timer_lane[1], lanes.timer_lane[2]) \ No newline at end of file | 104 | print (lanes.timer_lane[1], lanes.timer_lane[2]) \ No newline at end of file |