From 4d15fb415ca99cb7e9b5e2a44d589a661d1960c4 Mon Sep 17 00:00:00 2001 From: Johan Van Kerckhoven Date: Fri, 6 Oct 2023 11:49:11 +0200 Subject: [PATCH 1/4] Bugfix in priority of put!/get of a resource * put!/get (lock/unlock) requests for a resource are now handled in descending priority order, the same way as simulation events. --- src/resources/base.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/base.jl b/src/resources/base.jl index 70296e6..2ae9945 100755 --- a/src/resources/base.jl +++ b/src/resources/base.jl @@ -23,7 +23,7 @@ struct Get <: ResourceEvent end function isless(a::ResourceKey, b::ResourceKey) - (a.priority < b.priority) || (a.priority === b.priority && a.id < b.id) + (a.priority > b.priority) || (a.priority === b.priority && a.id < b.id) end function trigger_put(put_ev::ResourceEvent, res::AbstractResource) From ce0fbd852f01e12f4b892b418558fa007d5a2f80 Mon Sep 17 00:00:00 2001 From: Johan Van Kerckhoven Date: Tue, 10 Oct 2023 07:49:54 +0200 Subject: [PATCH 2/4] Implementation change of put!/get bugfix * Changed the implementation of the bugfix to priority handling for put!/get requests to avoid changing the meaning of `isless` as per the request of @Krastanov in PR #101. --- src/resources/base.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/resources/base.jl b/src/resources/base.jl index 2ae9945..81121a1 100755 --- a/src/resources/base.jl +++ b/src/resources/base.jl @@ -23,11 +23,11 @@ struct Get <: ResourceEvent end function isless(a::ResourceKey, b::ResourceKey) - (a.priority > b.priority) || (a.priority === b.priority && a.id < b.id) + (a.priority < b.priority) || (a.priority === b.priority && a.id > b.id) end function trigger_put(put_ev::ResourceEvent, res::AbstractResource) - queue = DataStructures.PriorityQueue(res.put_queue) + queue = DataStructures.PriorityQueue(Base.Order.Reverse, res.put_queue) while length(queue) > 0 (put_ev, key) = DataStructures.peek(queue) proceed = do_put(res, put_ev, key) @@ -37,7 +37,7 @@ function trigger_put(put_ev::ResourceEvent, res::AbstractResource) end function trigger_get(get_ev::ResourceEvent, res::AbstractResource) - queue = DataStructures.PriorityQueue(res.get_queue) + queue = DataStructures.PriorityQueue(Base.Order.Reverse, res.get_queue) while length(queue) > 0 (get_ev, key) = DataStructures.peek(queue) proceed = do_get(res, get_ev, key) From 16ded7cd6666eaa0820b7310f59396bc89360f9b Mon Sep 17 00:00:00 2001 From: Johan Van Kerckhoven Date: Mon, 13 Nov 2023 11:40:19 +0100 Subject: [PATCH 3/4] Added option to choose order of event handling * Added an optional keyword argument, `highpriofirst` to Simulation, Container, Store, and their derivatives, so the user can select how they wish priorities to be handled (`true` for high to low, `false` otherwise). * Default for Simulation is `true`, default for Container and Store is `false` so as not to break existing code. --- Project.toml | 4 ++-- src/base.jl | 6 ++++++ src/resources/base.jl | 14 +++++++++++--- src/resources/containers.jl | 4 ++-- src/resources/stores.jl | 4 ++-- src/simulations.jl | 10 +++++++--- test/test_resource_priorities.jl | 19 ++++++++++++++++++- 7 files changed, 48 insertions(+), 13 deletions(-) diff --git a/Project.toml b/Project.toml index 7a6eccd..4c5e958 100644 --- a/Project.toml +++ b/Project.toml @@ -1,11 +1,11 @@ name = "ConcurrentSim" uuid = "6ed1e86c-fcaf-46a9-97e0-2b26a2cdb499" -keywords = ["discrete-even simulation"] +keywords = ["discrete-event simulation"] license = "MIT" desc = "A discrete event process oriented simulation framework." authors = ["Ben Lauwens and SimJulia and ConcurrentSim contributors"] repo = "https://github.com/JuliaDynamics/ConcurrentSim.jl.git" -version = "1.4.0" +version = "1.4.1" [deps] DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" diff --git a/src/base.jl b/src/base.jl index 081bfc1..d1c4ac5 100755 --- a/src/base.jl +++ b/src/base.jl @@ -22,6 +22,12 @@ mutable struct BaseEvent end end +struct HighPrioFirst <: Base.Ordering end +struct LowPrioFirst <: Base.Ordering end +const HighPrio = HighPrioFirst() +const LowPrio = LowPrioFirst() +pickorder( hpf::Bool ) = hpf ? HighPrio : LowPrio + function show(io::IO, ev::AbstractEvent) print(io, "$(typeof(ev)) $(ev.bev.id)") end diff --git a/src/resources/base.jl b/src/resources/base.jl index 81121a1..8f7987c 100755 --- a/src/resources/base.jl +++ b/src/resources/base.jl @@ -22,12 +22,20 @@ struct Get <: ResourceEvent end end +function Base.lt( ::HighPrioFirst, a::ResourceKey, b::ResourceKey ) + (a.priority > b.priority) || (a.priority === b.priority && a.id < b.id) +end + +function Base.lt( ::LowPrioFirst, a::ResourceKey, b::ResourceKey ) + (a.priority < b.priority) || (a.priority === b.priority && a.id < b.id) +end + function isless(a::ResourceKey, b::ResourceKey) - (a.priority < b.priority) || (a.priority === b.priority && a.id > b.id) + (a.priority < b.priority) || (a.priority === b.priority && a.id < b.id) end function trigger_put(put_ev::ResourceEvent, res::AbstractResource) - queue = DataStructures.PriorityQueue(Base.Order.Reverse, res.put_queue) + queue = DataStructures.PriorityQueue(res.put_queue.o, res.put_queue) while length(queue) > 0 (put_ev, key) = DataStructures.peek(queue) proceed = do_put(res, put_ev, key) @@ -37,7 +45,7 @@ function trigger_put(put_ev::ResourceEvent, res::AbstractResource) end function trigger_get(get_ev::ResourceEvent, res::AbstractResource) - queue = DataStructures.PriorityQueue(Base.Order.Reverse, res.get_queue) + queue = DataStructures.PriorityQueue(res.get_queue.o, res.get_queue) while length(queue) > 0 (get_ev, key) = DataStructures.peek(queue) proceed = do_get(res, get_ev, key) diff --git a/src/resources/containers.jl b/src/resources/containers.jl index 9cf063a..a45cfc7 100755 --- a/src/resources/containers.jl +++ b/src/resources/containers.jl @@ -27,8 +27,8 @@ mutable struct Container{N<:Real, T<:Number} <: AbstractResource seid :: UInt put_queue :: DataStructures.PriorityQueue{Put, ContainerKey{N, T}} get_queue :: DataStructures.PriorityQueue{Get, ContainerKey{N, T}} - function Container{N, T}(env::Environment, capacity::N=one(N); level=zero(N)) where {N<:Real, T<:Number} - new(env, capacity, N(level), zero(UInt), DataStructures.PriorityQueue{Put, ContainerKey{N, T}}(), DataStructures.PriorityQueue{Get, ContainerKey{N, T}}()) + function Container{N, T}(env::Environment, capacity::N=one(N); level=zero(N), highpriofirst::Bool=false) where {N<:Real, T<:Number} + new(env, capacity, N(level), zero(UInt), DataStructures.PriorityQueue{Put, ContainerKey{N, T}}( pickorder(highpriofirst) ), DataStructures.PriorityQueue{Get, ContainerKey{N, T}}( pickorder(highpriofirst) )) end end diff --git a/src/resources/stores.jl b/src/resources/stores.jl index 67e86fd..320c750 100755 --- a/src/resources/stores.jl +++ b/src/resources/stores.jl @@ -42,8 +42,8 @@ mutable struct Store{N, T<:Number, D} <: AbstractResource seid :: UInt put_queue :: DataStructures.PriorityQueue{Put, StorePutKey{N, T}} get_queue :: DataStructures.PriorityQueue{Get, StoreGetKey{T}} - function Store{N, T, D}(env::Environment; capacity=typemax(UInt)) where {N, T<:Number, D} - new(env, UInt(capacity), zero(UInt), D(), zero(UInt), DataStructures.PriorityQueue{Put, StorePutKey{N, T}}(), DataStructures.PriorityQueue{Get, StoreGetKey{T}}()) + function Store{N, T, D}(env::Environment; capacity=typemax(UInt), highpriofirst::Bool=false) where {N, T<:Number, D} + new(env, UInt(capacity), zero(UInt), D(), zero(UInt), DataStructures.PriorityQueue{Put, StorePutKey{N, T}}( pickorder(highpriofirst) ), DataStructures.PriorityQueue{Get, StoreGetKey{T}}( pickorder(highpriofirst) )) end end diff --git a/src/simulations.jl b/src/simulations.jl index b26b2c0..935ad9c 100755 --- a/src/simulations.jl +++ b/src/simulations.jl @@ -14,18 +14,22 @@ struct EventKey id :: UInt end -function isless(a::EventKey, b::EventKey) :: Bool +function Base.lt( ::HighPrioFirst, a::EventKey, b::EventKey ) :: Bool (a.time < b.time) || (a.time === b.time && a.priority > b.priority) || (a.time === b.time && a.priority === b.priority && a.id < b.id) end +function Base.lt( ::LowPrioFirst, a::EventKey, b::EventKey ) :: Bool + (a.time < b.time) || (a.time === b.time && a.priority < b.priority) || (a.time === b.time && a.priority === b.priority && a.id < b.id) +end + mutable struct Simulation <: Environment time :: Float64 heap :: DataStructures.PriorityQueue{BaseEvent, EventKey} eid :: UInt sid :: UInt active_proc :: Union{AbstractProcess, Nothing} - function Simulation(initial_time::Number=zero(Float64)) - new(initial_time, DataStructures.PriorityQueue{BaseEvent, EventKey}(), zero(UInt), zero(UInt), nothing) + function Simulation(initial_time::Number=zero(Float64), highpriofirst::Bool=true) + new(initial_time, DataStructures.PriorityQueue{BaseEvent, EventKey}( pickorder(highpriofirst) ), zero(UInt), zero(UInt), nothing) end end diff --git a/test/test_resource_priorities.jl b/test/test_resource_priorities.jl index 6343e2f..8edc89d 100644 --- a/test/test_resource_priorities.jl +++ b/test/test_resource_priorities.jl @@ -61,6 +61,8 @@ put!(sto, :b; priority = typemin(UInt)) @testset "Resource priority evaluation" begin using ResumableFunctions + println( "Resource request 1 with priority 5, resource request 2 with priority 0" ) + let sim = Simulation() @resumable function f(env, res) @yield lock(res) @@ -71,6 +73,21 @@ put!(sto, :b; priority = typemin(UInt)) ev2 = unlock(res) @process f(sim, res) run(sim) - @test state(ev1) === ConcurrentSim.processed + + println( "Default request order (low prio first): request ", state(ev1) === ConcurrentSim.processed ? 1 : 2, " served first." ) + end + + let sim = Simulation() + @resumable function f(env, res) + @yield lock(res) + end + + res = Resource(sim, highpriofirst=true) + ev1 = unlock(res, priority=5) + ev2 = unlock(res) + @process f(sim, res) + run(sim) + + println( "Alternate request order (high prio first): request ", state(ev1) === ConcurrentSim.processed ? 1 : 2, " served first." ) end end \ No newline at end of file From 45a41c785c98dd94bc7c81546f968e57d6bbfb18 Mon Sep 17 00:00:00 2001 From: Johan Van Kerckhoven Date: Mon, 13 Nov 2023 11:58:34 +0100 Subject: [PATCH 4/4] Bugfix to reimplementation + doc update * Added the optional keyword argument to all methods and Resources derived from base Container and Store. * Added a line about the keyword argument in the docstring of Container and Store. --- src/resources/containers.jl | 12 +++++++----- src/resources/delayed_stores.jl | 8 ++++---- src/resources/ordered_stores.jl | 4 ++-- src/resources/stores.jl | 12 +++++++----- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/resources/containers.jl b/src/resources/containers.jl index a45cfc7..1b599f3 100755 --- a/src/resources/containers.jl +++ b/src/resources/containers.jl @@ -5,7 +5,7 @@ struct ContainerKey{N<:Real, T<:Number} <: ResourceKey end """ - Container{N<:Real, T<:Number}(env::Environment, capacity::N=one(N); level::N=zero(N)) + Container{N<:Real, T<:Number}(env::Environment, capacity::N=one(N); level::N=zero(N), highpriofirst::Bool=false) A "Container" resource object, storing up to `capacity` units of a resource (of type `N`). @@ -16,6 +16,8 @@ The [`lock`](@ref) and [`unlock`](@ref) functions are a convenient way to intera in a way mostly compatible with other discrete event and concurrency frameworks. The `request` and `release` aliases are also available for these two functions. +`highpriofirst` determines the order of handling requests that can be met at the same time. + See [`Store`](@ref) for a more channel-like resource. Think of `Resource` and `Container` as locks and of `Store` as channels. They block only if empty (on taking) or full (on storing). @@ -32,12 +34,12 @@ mutable struct Container{N<:Real, T<:Number} <: AbstractResource end end -function Container(env::Environment, capacity::N=one(N); level=zero(N)) where {N<:Real} - Container{N, Int}(env, capacity; level=N(level)) +function Container(env::Environment, capacity::N=one(N); level=zero(N), highpriofirst::Bool=false) where {N<:Real} + Container{N, Int}(env, capacity; level=N(level), highpriofirst=highpriofirst) end -function Container{T}(env::Environment, capacity::N=one(N); level=zero(N)) where {N<:Real, T<:Number} - Container{N, T}(env, capacity; level=N(level)) +function Container{T}(env::Environment, capacity::N=one(N); level=zero(N), highpriofirst::Bool=false) where {N<:Real, T<:Number} + Container{N, T}(env, capacity; level=N(level), highpriofirst=highpriofirst) end const Resource = Container{Int, Int} diff --git a/src/resources/delayed_stores.jl b/src/resources/delayed_stores.jl index bad21b1..f456e5e 100755 --- a/src/resources/delayed_stores.jl +++ b/src/resources/delayed_stores.jl @@ -37,11 +37,11 @@ mutable struct DelayQueue{T} store::QueueStore{T, Int} delay::Float64 end -function DelayQueue(env::Environment, delay) - return DelayQueue(QueueStore{Any}(env), float(delay)) +function DelayQueue(env::Environment, delay; highpriofirst::Bool=false) + return DelayQueue(QueueStore{Any}(env, highpriofirst=highpriofirst), float(delay)) end -function DelayQueue{T}(env::Environment, delay) where T - return DelayQueue(QueueStore{T}(env), float(delay)) +function DelayQueue{T}(env::Environment, delay; highpriofirst::Bool=false) where T + return DelayQueue(QueueStore{T}(env, highpriofirst=highpriofirst), float(delay)) end @resumable function latency(env::Environment, channel::DelayQueue, value) diff --git a/src/resources/ordered_stores.jl b/src/resources/ordered_stores.jl index 5923520..3348e51 100755 --- a/src/resources/ordered_stores.jl +++ b/src/resources/ordered_stores.jl @@ -31,7 +31,7 @@ julia> [value(take!(stack)) for _ in 1:length(items)] See also: [`QueueStore`](@ref), [`Store`](@ref) """ const StackStore = Store{N, T, DataStructures.Stack{N}} where {N, T<:Number} -StackStore{N}(env::Environment; capacity=typemax(UInt)) where {N} = StackStore{N, Int}(env; capacity) +StackStore{N}(env::Environment; capacity=typemax(UInt), highpriofirst::Bool=false) where {N} = StackStore{N, Int}(env; capacity, highpriofirst=highpriofirst) """ QueueStore{N, T<:Number} @@ -66,7 +66,7 @@ julia> [value(take!(queue)) for _ in 1:length(items)] See also: [`StackStore`](@ref), [`Store`](@ref) """ const QueueStore = Store{N, T, DataStructures.Queue{N}} where {N, T<:Number} -QueueStore{N}(env::Environment; capacity=typemax(UInt)) where {N} = QueueStore{N, Int}(env; capacity) +QueueStore{N}(env::Environment; capacity=typemax(UInt), highpriofirst::Bool=false) where {N} = QueueStore{N, Int}(env; capacity, highpriofirst=highpriofirst) function do_put(sto::StackStore{N, T}, put_ev::Put, key::StorePutKey{N, T}) where {N, T<:Number} if sto.load < sto.capacity diff --git a/src/resources/stores.jl b/src/resources/stores.jl index 320c750..875b8e3 100755 --- a/src/resources/stores.jl +++ b/src/resources/stores.jl @@ -11,11 +11,13 @@ struct StoreGetKey{T<:Number} <: ResourceKey end """ - Store{N, T<:Number}(env::Environment; capacity::UInt=typemax(UInt)) + Store{N, T<:Number}(env::Environment; capacity::UInt=typemax(UInt), highpriofirst::Bool=false) A store is a resource that can hold a number of items of type `N`. It is similar to a `Base.Channel` with a finite capacity ([`put!`](@ref) blocks after reaching capacity). The [`put!`](@ref) and [`take!`](@ref) functions are a convenient way to interact with such a "channel" in a way mostly compatible with other discrete event and concurrency frameworks. +`highpriofirst` determines the order of handling requests that can be met at the same time. + See [`Container`](@ref) for a more lock-like resource. Think of `Resource` and `Container` as locks and of `Store` as channels/stacks. They block only if empty (on taking) or full (on storing). @@ -47,12 +49,12 @@ mutable struct Store{N, T<:Number, D} <: AbstractResource end end -function Store{N, T}(env::Environment; capacity=typemax(UInt)) where {N, T<:Number} - Store{N, T, Dict{N, UInt}}(env; capacity=UInt(capacity)) +function Store{N, T}(env::Environment; capacity=typemax(UInt), highpriofirst::Bool=false) where {N, T<:Number} + Store{N, T, Dict{N, UInt}}(env; capacity=UInt(capacity), highpriofirst=highpriofirst) end -function Store{N}(env::Environment; capacity=typemax(UInt)) where {N} - Store{N, Int}(env; capacity=UInt(capacity)) +function Store{N}(env::Environment; capacity=typemax(UInt), highpriofirst::Bool=false) where {N} + Store{N, Int}(env; capacity=UInt(capacity), highpriofirst=highpriofirst) end """