The documentation for sleep() doesn’t mention that yield wakes it up, which I guess makes it an undocumented feature…But I’d really like to utilize that if possible…
Example:
using Dates
task = schedule(Task() do
sleepAt = now()
sleep(10)
println("Slept for $(now() - sleepAt)")
end)
sleep(2)
yield(task)
If not I can use the wait() call on a condition that’s never triggered. (Triggering it on shutdown is possible, but would require that these tasks be treated special from the other tasks during shutdown…and I’d prefer to avoid that.)
It’s not necessarily that yield()
wakes up sleep()
calls specifically, but instead that once the timer backing a sleep()
call has elapsed, the task that contains that sleep()
call is now eligible to be woken up by the scheduler (which a yield()
call may cause to happen). This is also true for tasks waiting on I/O or other syscalls, with the obvious difference being that their condition for waking their waiting task is different than for sleep()
.
If your goal is to wait on a task to wake up, then you’ll need to place a Condition()
within the task and notify()
it once your task has woken up (which a wait()
call can then wait on). If you’re just waiting for the sleeping task to complete, then just wait()
on it.
EDIT: To answer the question in your title, I would say that no, you cannot assume that a yield()
call will wake up your task, because you can arbitrarily many tasks waiting in the scheduler queue ahead of your now-woken task that may be scheduled first. However, if we assume that the scheduler runs over the queue round-robin style, then yes you can assume that your sleeping task has woken once the yield()
call returns, because the yield()
-ing task will end up executing chronologically after the previously sleeping task executes.
Okay so taking your answer it’s an undocumented reaction, because with the above code, the task “wakes up” after 2 seconds not 10.
Reading the wait
documentation closer it only mentions the “no argument” version waiting for an undefined period and only being restarted in response to a schedule
or yieldto
call. The documentation for yieldto
specifically mentions Its use is discouraged.
But the documentation for yield
says it calls schedule for the task, so I guess that works.
If I don’t have a documented way to break a sleep then I guess I’ll have to use 2 task, one that does the actual work, that I can ensure is stopped at shutdown (by setting a shutdown flag and notifying it’s condition) and a second that periodically wakes up and trigger’s the first’s condition.
Granted the documentation for wait
say it’s often in a while loop to ensure a waited-for condition is met before proceeding…and the condition object is edge triggered so I can’t check that…ugh it’s either boolean channels or more flags.
Sorry, I didn’t realize that this was unexpected; yield()
will immediately return if there are no other tasks to be scheduled, effectively making it a no-op. If that 10 second timer hasn’t elapsed, it is not possible or safe to wake the task.
So this is a new requirement that wasn’t mentioned before (or I didn’t notice it). I think it would help if you could give a more real-world example of what you’re trying to accomplish, otherwise I feel like I won’t be as useful in giving you advice.
To be fair, your example code does produce an error that crashes julia 1.1 a couple seconds later:
julia> using Dates
julia> task = schedule(Task() do
sleepAt = now()
sleep(10)
println("Slept for $(now() - sleepAt)")
end)
Task (runnable) @0x00007fcbc60fc760
julia> sleep(2); yield(task)
Slept for 3265 milliseconds
julia> ERROR: schedule: Task not runnable
Stacktrace:
[1] enq_work at ./event.jl:88 [inlined]
[2] #schedule#443(::Bool, ::Function, ::Task, ::EOFError) at ./event.jl:134
Ahhh, that’s interesting I had not seen that crash on version 1.1.1. However if I add a sleep() after the yield(task) so now I see the crash…Well that options is out.
Real world examples get long, but here you go, given everything I’ve read/seen it sounds like I’m going to have to resort to this basic flow:
using Dates
using Base.Threads
shutdown = Atomic{Bool}(false)
wakeup = Channel{Bool}(3)
wakeTask = schedule(Task() do
local wakeAt = now() + Second(10)
while shutdown[] == false
if now() >= wakeAt
put!(wakeup, true)
wakeAt = now() + Second(10)
else
sleep(Dates.value(wakeAt-now()) / 1000)
end
end
end)
workTask = schedule(Task() do
while take!(wakeup) == true
println("Heart beat")
end
println("Cleanup")
end)
function stop()
atomic_xchg!(shutdown, true)
put!(wakeup, false)
wait(workTask)
end
I guess I was just hoping for less code. Which an early way to wake up sleep would give…or a wait with a timeout. But I don’t have either of those, so this is probably what I’ll use.
- The wakeTask feels gratuitous, but I don’t see a way around it.
- The channel is dangerous in the aspect that if the workTask stops unexpectedly/crashes then
stop()
could hang forever. So I’ll want to add protection for that.
- I can’t really use a Condition because the wait documentation says wait could return before the condition is set and I don’t want my heart beating too fast.
- I might be able to remove the checks around sleep, it’s documentation doesn’t mention waking up early…
In my real world the delays are not in seconds but hours…so I really would like a way to shutdown workTask early so it can save it’s state.