Skip to content

Commit bd473f4

Browse files
authored
implement DelayQueue (#99)
1 parent 91c7e36 commit bd473f4

File tree

8 files changed

+101
-2
lines changed

8 files changed

+101
-2
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# News
22

3+
## v1.4.0 - 2023-08-07
4+
5+
- Implement a `DelayQueue`, i.e. a `QueueStore` with latency between the store and take events.
6+
- Bugfix to `QueueStore` and `StackStore` for take events on empty stores.
7+
38
## v1.3.0 - 2023-08-07
49

510
- Implement ordered versions of `Store`, namely `QueueStore` and `StackStore`.

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ license = "MIT"
55
desc = "A discrete event process oriented simulation framework."
66
authors = ["Ben Lauwens and SimJulia and ConcurrentSim contributors"]
77
repo = "https://github.com/JuliaDynamics/ConcurrentSim.jl.git"
8-
version = "1.3.0"
8+
version = "1.4.0"
99

1010
[deps]
1111
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"

docs/src/examples/Latency.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11

22
# Event Latency
33

4+
There is a built-in [`DelayQueue`](@ref) if you need a store [`Store`](@ref) with latency between `put!` and `take!` events. However here, we show you how you could have built one for yourself. If you modify this in order to construct a particularly useful type of latency store, please contribute it to the library through a pull request.
5+
46
## Description
57
In this example we show how to separate the time delay between processes from the processes themselves. We model a communications channel, called a `Cable`, where a sender sends messages regularly each `SEND_PERIOD` time units and a receiver listens each `RECEIVE_PERIOD`. The messages in the cable have a delay fo `DELAY_DURATION` until they reach the recevier.
68

src/ConcurrentSim.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ module ConcurrentSim
1717
export @resumable, @yield
1818
export AbstractProcess, Simulation, run, now, active_process, StopSimulation
1919
export Process, @process, interrupt
20-
export Container, Resource, Store, StackStore, QueueStore, put!, get, cancel, request, tryrequest, release
20+
export Container, Resource, Store, StackStore, QueueStore, DelayQueue
21+
export put!, get, cancel, request, tryrequest, release
2122
export nowDatetime
2223

2324
include("base.jl")
@@ -29,6 +30,7 @@ module ConcurrentSim
2930
include("resources/containers.jl")
3031
include("resources/stores.jl")
3132
include("resources/ordered_stores.jl")
33+
include("resources/delayed_stores.jl")
3234
include("utils/time.jl")
3335
include("deprecated_aliased.jl")
3436
end

src/resources/delayed_stores.jl

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
@doc raw"""
2+
DelayQueue{T}
3+
4+
A queue in which items are stored in a FIFO order, but are only available after a delay.
5+
6+
```jldoctest
7+
julia> sim = Simulation()
8+
queue = DelayQueue{Symbol}(sim, 10)
9+
@resumable function producer(env, queue)
10+
for item in [:a,:b,:a,:c]
11+
@info "putting $item at time $(now(env))"
12+
put!(queue, item)
13+
@yield timeout(env, 2)
14+
end
15+
end
16+
@resumable function consumer(env, queue)
17+
@yield timeout(env, 5)
18+
while true
19+
t = @yield take!(queue)
20+
@info "taking $(t) at time $(now(env))"
21+
end
22+
end
23+
@process producer(sim, queue)
24+
@process consumer(sim, queue)
25+
run(sim, 30)
26+
[ Info: putting a at time 0.0
27+
[ Info: putting b at time 2.0
28+
[ Info: putting a at time 4.0
29+
[ Info: putting c at time 6.0
30+
[ Info: taking a at time 10.0
31+
[ Info: taking b at time 12.0
32+
[ Info: taking a at time 14.0
33+
[ Info: taking c at time 16.0
34+
```
35+
"""
36+
mutable struct DelayQueue{T}
37+
store::QueueStore{T, Int}
38+
delay::Float64
39+
end
40+
function DelayQueue(env::Environment, delay)
41+
return DelayQueue(QueueStore{Any}(env), float(delay))
42+
end
43+
function DelayQueue{T}(env::Environment, delay) where T
44+
return DelayQueue(QueueStore{T}(env), float(delay))
45+
end
46+
47+
@resumable function latency(env::Environment, channel::DelayQueue, value)
48+
@yield timeout(channel.store.env, channel.delay)
49+
put!(channel.store, value)
50+
end
51+
52+
function Base.put!(channel::DelayQueue, value)
53+
@process latency(channel.store.env, channel, value) # start the process, but do not wait on it
54+
end
55+
56+
function Base.take!(channel::DelayQueue)
57+
get(channel.store)
58+
end

src/resources/ordered_stores.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ end
7979

8080
function do_get(sto::StackStore{N, T}, get_ev::Get, key::StoreGetKey{T}) where {N, T<:Number}
8181
key.filter !== get_any_item && error("Filtering not supported for `StackStore`. Use an unordered store instead, or submit a feature request for implementing filtering to our issue tracker.")
82+
isempty(sto.items) && return true
8283
item = pop!(sto.items)
8384
sto.load -= one(UInt)
8485
schedule(get_ev; value=item)
@@ -96,6 +97,7 @@ end
9697

9798
function do_get(sto::QueueStore{N, T}, get_ev::Get, key::StoreGetKey{T}) where {N, T<:Number}
9899
key.filter !== get_any_item && error("Filtering not supported for `QueueStore`. Use an unordered store instead, or submit a feature request for implementing filtering to our issue tracker.")
100+
isempty(sto.items) && return true
99101
item = dequeue!(sto.items)
100102
sto.load -= one(UInt)
101103
schedule(get_ev; value=item)

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ println("Starting tests with $(Threads.nthreads()) threads out of `Sys.CPU_THREA
3535
@doset "resources_containers_deprecated"
3636
@doset "resources_stores"
3737
@doset "resources_stores_deprecated"
38+
@doset "resources_fancy_stores"
3839
@doset "resource_priorities"
3940
@doset "utils_time"
4041
VERSION >= v"1.9" && @doset "doctests"

test/test_resources_fancy_stores.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using ConcurrentSim
2+
using ResumableFunctions
3+
4+
@resumable function producer(env, queue)
5+
for item in [:a,:b,:a,:c]
6+
@info "putting $item at time $(now(env))"
7+
put!(queue, item)
8+
@yield timeout(env, 2)
9+
end
10+
end
11+
@resumable function consumer(env, queue)
12+
@yield timeout(env, 5)
13+
while true
14+
t = @yield take!(queue)
15+
@info "taking $(t) at time $(now(env))"
16+
end
17+
end
18+
19+
function runsim(storeconstructor)
20+
sim = Simulation()
21+
queue = storeconstructor(sim)
22+
@process producer(sim, queue)
23+
@process consumer(sim, queue)
24+
run(sim, 30)
25+
end
26+
27+
runsim(sim->DelayQueue{Symbol}(sim, 10))
28+
runsim(sim->QueueStore{Symbol}(sim))
29+
runsim(sim->Store{Symbol}(sim))

0 commit comments

Comments
 (0)