@@ -103,14 +103,15 @@ end
103103@enum WorkerState W_CREATED W_CONNECTED W_TERMINATING W_TERMINATED W_UNKNOWN_STATE
104104mutable struct Worker
105105 id:: Int
106- msg_lock:: Threads. ReentrantLock # Lock for del_msgs, add_msgs, and gcflag
106+ msg_lock:: ReentrantLock # guards ` del_msgs`, ` add_msgs` , and ` gcflag`
107107 del_msgs:: Array{Any,1} # XXX : Could del_msgs and add_msgs be Channels?
108108 add_msgs:: Array{Any,1}
109109 @atomic gcflag:: Bool
110+ # `state` changes are published via the module-global `worker_state_cond`;
111+ # wait on that (re-checking this atomic) rather than a per-worker condition
110112 @atomic state:: WorkerState
111- c_state:: Threads.Condition # wait for state changes, lock for state
112- ct_time:: Float64 # creation time
113- conn_func:: Any # used to setup connections lazily
113+ ct_time:: Float64 # creation time
114+ conn_func:: Any # used to setup connections lazily
114115
115116 r_stream:: IO
116117 w_stream:: IO
@@ -142,7 +143,7 @@ mutable struct Worker
142143 if haskey (map_pid_wrkr, id)
143144 return map_pid_wrkr[id]
144145 end
145- w= new (id, Threads . ReentrantLock (), [], [], false , W_CREATED, Threads . Condition () , time (), conn_func)
146+ w= new (id, ReentrantLock (), [], [], false , W_CREATED, time (), conn_func)
146147 w. initialized = Event ()
147148 register_worker (w)
148149 w
@@ -152,10 +153,10 @@ mutable struct Worker
152153end
153154
154155function set_worker_state (w, state)
155- lock (w . c_state) do
156- @atomic w . state = state
157- notify (w . c_state; all = true )
158- end
156+ @atomic w . state = state
157+ # Wake every task waiting on worker state/membership: `wait_for_conn`,
158+ # topology setup in `create_worker`, `_rmprocs`, `check_master_connect`, etc.
159+ @lock worker_state_cond notify (worker_state_cond; all = true )
159160end
160161
161162function check_worker_state (w:: Worker )
@@ -202,10 +203,14 @@ function wait_for_conn(w)
202203 timeout = worker_timeout () - (time () - w. ct_time)
203204 timeout <= 0 && error (" peer $(w. id) has not connected to $(myid ()) " )
204205
205- if timedwait (() -> (@atomic w. state) === W_CONNECTED, timeout) === :timed_out
206- # Notify any waiters on the state and throw
207- @lock w. c_state notify (w. c_state)
208- error (" peer $(w. id) didn't connect to $(myid ()) within $timeout seconds" )
206+ deadline = time_ns () + timeout * 1e9
207+ @lock worker_state_cond begin
208+ while (@atomic w. state) != = W_CONNECTED
209+ wait_or_deadline (worker_state_cond, deadline) || break
210+ end
211+ if (@atomic w. state) != = W_CONNECTED
212+ error (" peer $(w. id) didn't connect to $(myid ()) within $(worker_timeout ()) seconds" )
213+ end
209214 end
210215 end
211216 nothing
223228
224229worker_timeout () = parse (Float64, get (ENV , " JULIA_WORKER_TIMEOUT" , " 60.0" ))
225230
231+ # Wait for `notify` on the `Threads.Condition`. Triggers its own notification
232+ # when the absolute `deadline` (nanoseconds on the `time_ns()` clock; `Inf`
233+ # waits forever) arrives.
234+ #
235+ # Return `false` once the deadline has passed (so callers can
236+ # `wait_or_deadline(...) || break`) and `true` otherwise. The deadline causes
237+ # spurious wakeups for other waiters, so callers must re-check their own
238+ # condition in a loop.
239+ function wait_or_deadline (c:: Threads.Condition , deadline:: Real )
240+ remaining = (deadline - time_ns ()) / 1e9
241+ if ! isfinite (remaining)
242+ wait (c)
243+ return true
244+ end
245+ remaining <= 0 && return false
246+ timer = Timer (remaining) do _
247+ @lock c notify (c; all= true )
248+ end
249+ try
250+ wait (c)
251+ finally
252+ close (timer)
253+ end
254+ return true
255+ end
256+
226257
227258# # worker creation and setup ##
228259"""
@@ -363,7 +394,7 @@ function read_worker_host_port(io::IO)
363394
364395 # Wait at most for JULIA_WORKER_TIMEOUT seconds to read host:port
365396 # info from the worker
366- timeout = worker_timeout () * 1e9
397+ deadline = t0 + worker_timeout () * 1e9
367398 # We expect the first line to contain the host:port string. However, as
368399 # the worker may be launched via ssh or a cluster manager like SLURM,
369400 # ignore any informational / warning lines printed by the launch command.
@@ -372,12 +403,20 @@ function read_worker_host_port(io::IO)
372403
373404 ntries = 1000
374405 leader = String[]
406+ # notified when a spawned `readline` task finishes, so we can wait for a line
407+ # to arrive (or the overall timeout to elapse) without polling
408+ readcond = Threads. Condition ()
375409 try
376410 while ntries > 0
377- readtask = @async readline (io)
378- yield ()
379- while ! istaskdone (readtask) && ((time_ns () - t0) < timeout)
380- sleep (0.05 )
411+ readtask = @async try
412+ readline (io)
413+ finally
414+ @lock readcond notify (readcond; all= true )
415+ end
416+ @lock readcond begin
417+ while ! istaskdone (readtask)
418+ wait_or_deadline (readcond, deadline) || break
419+ end
381420 end
382421 ! istaskdone (readtask) && break
383422
@@ -713,9 +752,9 @@ function create_worker(manager::ClusterManager, wconfig::WorkerConfig)
713752 for jw in PGRP. workers
714753 if (jw. id != 1 ) && (jw. id < w. id)
715754 # wait for wl to join
716- if ( @atomic jw . state) === W_CREATED
717- lock ( jw. c_state) do
718- wait (jw . c_state )
755+ @lock worker_state_cond begin
756+ while ( @atomic jw. state) === W_CREATED
757+ wait (worker_state_cond )
719758 end
720759 end
721760 push! (join_list, jw)
@@ -727,23 +766,27 @@ function create_worker(manager::ClusterManager, wconfig::WorkerConfig)
727766 filterfunc (x) = (x. id != 1 ) && isdefined (x, :config ) &&
728767 (notnothing (x. config. ident) in something (wconfig. connect_idents, []))
729768
730- wlist = filter (filterfunc, PGRP. workers)
731- waittime = 0
732- while wconfig. connect_idents != = nothing &&
733- length (wlist) < length (wconfig. connect_idents)
734- if waittime >= timeout
735- error (" peer workers did not connect within $timeout seconds" )
769+ deadline = time_ns () + timeout * 1e9
770+ # Recompute `wlist` under the lock before each wait so we never miss a
771+ # `register_worker` notification that races with the filter (a lost
772+ # wakeup would stall this until the deadline).
773+ local wlist
774+ @lock worker_state_cond begin
775+ while true
776+ wlist = filter (filterfunc, PGRP. workers)
777+ (wconfig. connect_idents === nothing ||
778+ length (wlist) >= length (wconfig. connect_idents)) && break
779+ if ! wait_or_deadline (worker_state_cond, deadline)
780+ error (" peer workers did not connect within $timeout seconds" )
781+ end
736782 end
737- sleep (1.0 )
738- waittime += 1
739- wlist = filter (filterfunc, PGRP. workers)
740783 end
741784
742785 for wl in wlist
743- lock (wl . c_state) do
744- if (@atomic wl. state) === W_CREATED
786+ @ lock worker_state_cond begin
787+ while (@atomic wl. state) === W_CREATED
745788 # wait for wl to join
746- wait (wl . c_state )
789+ wait (worker_state_cond )
747790 end
748791 end
749792 push! (join_list, wl)
@@ -809,12 +852,18 @@ function check_master_connect()
809852 if ccall (:jl_running_on_valgrind ,Cint,()) != 0
810853 return
811854 end
812-
813855 errormonitor (
814856 @async begin
815- timeout = worker_timeout ()
816- if timedwait (() -> haskey (map_pid_wrkr, 1 ), timeout) === :timed_out
817- print (stderr , " Master process (id 1) could not connect within $(timeout) seconds.\n exiting.\n " )
857+ timeout = worker_timeout () * 1e9
858+ deadline = time_ns () + timeout
859+ @lock worker_state_cond begin
860+ while ! haskey (map_pid_wrkr, 1 )
861+ wait_or_deadline (worker_state_cond, deadline) || break
862+ end
863+ end
864+
865+ if ! haskey (map_pid_wrkr, 1 )
866+ print (stderr , " Master process (id 1) could not connect within $(timeout/ 1e9 ) seconds.\n exiting.\n " )
818867 exit (1 )
819868 end
820869 end
@@ -896,6 +945,11 @@ const LPROCROLE = Ref{Symbol}(:master)
896945const HDR_VERSION_LEN= 16
897946const HDR_COOKIE_LEN= 16
898947const map_pid_wrkr = Dict {Int, Union{Worker, LocalProcess}} ()
948+ # Notified whenever a worker is registered (added to `map_pid_wrkr`/`PGRP.workers`)
949+ # or changes state (via `set_worker_state`), so tasks can wait for peers or the
950+ # master to join the cluster. The lock guards reads/writes of the membership
951+ # collections done under it.
952+ const worker_state_cond = Threads. Condition ()
899953const map_sock_wrkr = IdDict ()
900954const map_del_wrkr = Set {Int} ()
901955
@@ -1130,10 +1184,15 @@ function _rmprocs(pids, waitfor)
11301184 end
11311185 end
11321186
1133- start = time_ns ()
1134- while (time_ns () - start) < waitfor* 1e9
1135- all (w -> (@atomic w. state) === W_TERMINATED, rmprocset) && break
1136- sleep (min (0.1 , waitfor - (time_ns () - start)/ 1e9 ))
1187+ # `waitfor == typemax(Int)` means wait indefinitely; otherwise bound the
1188+ # total wait across all workers by a single shared deadline
1189+ deadline = waitfor == typemax (Int) ? Inf : time_ns () + waitfor * 1e9
1190+ for w in rmprocset
1191+ @lock worker_state_cond begin
1192+ while (@atomic w. state) != = W_TERMINATED
1193+ wait_or_deadline (worker_state_cond, deadline) || break
1194+ end
1195+ end
11371196 end
11381197
11391198 unremoved = [wrkr. id for wrkr in filter (w -> (@atomic w. state) != = W_TERMINATED, rmprocset)]
@@ -1203,8 +1262,11 @@ end
12031262
12041263register_worker (w) = register_worker (PGRP, w)
12051264function register_worker (pg, w)
1206- push! (pg. workers, w)
1207- map_pid_wrkr[w. id] = w
1265+ @lock worker_state_cond begin
1266+ push! (pg. workers, w)
1267+ map_pid_wrkr[w. id] = w
1268+ notify (worker_state_cond; all= true )
1269+ end
12081270end
12091271
12101272function register_worker_streams (w)
0 commit comments