Skip to content

Commit

Permalink
undo lock/trylock and use request/tryrequest instead
Browse files Browse the repository at this point in the history
  • Loading branch information
Krastanov committed Aug 2, 2023
1 parent 642c26a commit 718f1db
Show file tree
Hide file tree
Showing 15 changed files with 81 additions and 33 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# News

## v1.0.1 - dev
## v1.1.0 - 2023-08-02

- Start using `Base`'s API: `lock`, `trylock`, `unlock`, `islocked`, `isready`, `put!`, `take!`. Deprecate `put`, `request`, `release`. Moreover, consider using `take!` instead of `get` (which was not deprecated as it has numerous internal uses).
- Start using `Base`'s API: `Base.unlock`, `Base.islocked`, `Base.isready`, `Base.put!`, `Base.take!`. Deprecate `put`, `release`. Moreover, consider using `Base.take!` instead of `Base.get` (which was not deprecated yet, as we decide which semantics to follow). Lastly, `Base.lock` and `Base.trylock` are **not** implement -- they are superficially similar to `request` and `tryrequest`, but have to be explicitly `@yield`-ed.
- Implement `tryrequest` (similar to `Base.trylock`). However, consider also using `Base.isready` and `request` instead of `tryrequest`.

## v1.0.0 - 2023-05-03

Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ 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.0.1"
version = "1.1.0"

[deps]
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
Expand Down
4 changes: 2 additions & 2 deletions benchmark/old_processes_MM1.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ function exp_source(sim::Simulation, lambd::Float64, server::Resource, mu::Float
end

function customer(sim::Simulation, server::Resource, mu::Float64)
yield(lock(server))
yield(request(server))
dt = rand(Exponential(1/mu))
yield(timeout(sim, dt))
yield(unlock(server))
end

function customer2(sim::Simulation, server::Resource, mu::Float64)
lock(server) do req
request(server) do req
yield(req)
dt = rand(Exponential(1/mu))
yield(timeout(sim, dt))
Expand Down
2 changes: 1 addition & 1 deletion benchmark/processes_MM1.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ using ResumableFunctions, ConcurrentSim, Distributions, BenchmarkTools
end

@resumable function customer(sim::Simulation, server::Resource, mu::Float64)
@yield lock(server)
@yield request(server)
dt = rand(Exponential(1 / mu))
@yield timeout(sim, dt)
@yield unlock(server)
Expand Down
2 changes: 0 additions & 2 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ Private = false
```

```@docs
lock(res::Container; priority::Int=0)
unlock(res::Container; priority::Int=0)
trylock(res::Container; priority::Int=0)
take!(sto::Store, filter::Function=get_any_item; priority::Int=0)
```
2 changes: 1 addition & 1 deletion docs/src/examples/mmc.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ service_dist = Exponential(1 / mu) # service time distribution
@resumable function customer(env::Environment, server::Resource, id::Integer, t_a::Float64, d_s::Distribution)
@yield timeout(env, t_a) # customer arrives
println("Customer $id arrived: ", now(env))
@yield lock(server) # customer starts service
@yield request(server) # customer starts service
println("Customer $id entered service: ", now(env))
@yield timeout(env, rand(d_s)) # server is busy
@yield unlock(server) # customer exits service
Expand Down
2 changes: 1 addition & 1 deletion docs/src/examples/ross.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const G = Exponential(MU)
else
throw(StopSimulation("No more spares!"))
end
@yield lock(repair_facility)
@yield request(repair_facility)
@yield timeout(env, rand(rng, G))
@yield unlock(repair_facility)
@yield put!(spares, active_process(env))
Expand Down
2 changes: 1 addition & 1 deletion docs/src/guides/environments.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ julia> isnothing(active_process(sim))
true
```

An exemplary use case for this is the resource system: If a process function calls `lock` to request a `Resource`, the resource determines the requesting process via `active_process`.
An exemplary use case for this is the resource system: If a process function calls `request` to request (lock) a `Resource`, the resource determines the requesting process via `active_process`.

## Miscellaneous

Expand Down
10 changes: 5 additions & 5 deletions docs/src/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ julia> using ConcurrentSim
julia> @resumable function car(env::Environment, name::Int, bcs::Resource, driving_time::Number, charge_duration::Number)
@yield timeout(sim, driving_time)
println(name, " arriving at ", now(env))
@yield lock(bcs)
@yield request(bcs)
println(name, " starting to charge at ", now(env))
@yield timeout(sim, charge_duration)
println(name, " leaving the bcs at ", now(env))
Expand All @@ -310,7 +310,7 @@ julia> @resumable function car(env::Environment, name::Int, bcs::Resource, drivi
car (generic function with 1 method)
```

The resource’s `lock` function generates an event that lets you wait until the resource becomes available again. If you are resumed, you “own” the resource until you release it with `unlock`.
The resource’s `request` function generates an event that lets you wait until the resource becomes available again. If you are resumed, you “own” the resource until you release it with `unlock`.

You are responsible to call `unlock` once you are done using the resource. When you unlock (release) a resource, the next waiting process is resumed and now “owns” one of the resource’s slots. The basic `Resource` sorts waiting processes in a FIFO (first in—first out) way.

Expand All @@ -324,7 +324,7 @@ DocTestSetup = quote
@resumable function car(env::Environment, name::Int, bcs::Resource, driving_time::Number, charge_duration::Number)
@yield timeout(sim, driving_time)
println(name, " arriving at ", now(env))
@yield lock(bcs)
@yield request(bcs)
println(name, " starting to charge at ", now(env))
@yield timeout(sim, charge_duration)
println(name, " leaving the bcs at ", now(env))
Expand All @@ -351,7 +351,7 @@ DocTestSetup = quote
@resumable function car(env::Environment, name::Int, bcs::Resource, driving_time::Number, charge_duration::Number)
@yield timeout(sim, driving_time)
println(name, " arriving at ", now(env))
@yield lock(bcs)
@yield request(bcs)
println(name, " starting to charge at ", now(env))
@yield timeout(sim, charge_duration)
println(name, " leaving the bcs at ", now(env))
Expand Down Expand Up @@ -393,7 +393,7 @@ DocTestSetup = quote
@resumable function car(env::Environment, name::Int, bcs::Resource, driving_time::Number, charge_duration::Number)
@yield timeout(sim, driving_time)
println(name, " arriving at ", now(env))
@yield lock(bcs)
@yield request(bcs)
println(name, " starting to charge at ", now(env))
@yield timeout(sim, charge_duration)
println(name, " leaving the bcs at ", now(env))
Expand Down
2 changes: 1 addition & 1 deletion src/ConcurrentSim.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module ConcurrentSim
export @resumable, @yield
export AbstractProcess, Simulation, run, now, active_process, StopSimulation
export Process, @process, interrupt
export Container, Resource, Store, put!, get, cancel
export Container, Resource, Store, put!, get, cancel, request, tryrequest
export nowDatetime

include("base.jl")
Expand Down
3 changes: 2 additions & 1 deletion src/deprecated.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
Base.@deprecate put(args...) put!(args...)
Base.@deprecate request(args...; kwargs...) lock(args...; kwargs...)
#Base.@deprecate request(args...; kwargs...) lock(args...; kwargs...) # Not the same: `request` needs to be yielded, while `lock` yields itself
#Base.@deprecate tryrequest(args...; kwargs...) trylock(args...; kwargs...) # Not the same: `request` needs to be yielded, while `lock` yields itself
Base.@deprecate release(args...; kwargs...) unlock(args...; kwargs...)
35 changes: 21 additions & 14 deletions src/resources/containers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ A "Container" resource object, storing up to `capacity` units of a resource (of
There is a `Resource` alias for `Container{Int}`.
`Resource()` with default capacity of `1` is very similar to a typical lock.
The [`lock`](@ref), [`unlock`](@ref), and [`trylock`](@ref) functions are a convenient way to interact with such a "lock",
The [`request`](@ref) and [`unlock`](@ref) functions are a convenient way to interact with such a "lock",
in a way mostly compatible with other discrete event and concurrency frameworks.
See [`Store`](@ref) for a more channel-like resource.
Expand Down Expand Up @@ -46,34 +46,39 @@ function put!(con::Container{N}, amount::N; priority::Int=0) where N<:Real
end

"""
lock(res::Container)
request(res::Container)
Locks the Container and return the lock event.
Locks the Container (or Resources) and return the lock event.
If the capacity of the Container is greater than 1,
multiple requests can be made before blocking occurs.
"""
lock(res::Container; priority::Int=0) = put!(res, 1; priority=priority)
request(res::Container; priority::Int=0) = put!(res, 1; priority=priority)

"""
trylock(res::Resource)
tryrequest(res::Container)
If the Resource is not locked, locks it and return the lock event.
Returns `false` if the Resource is locked, similarly to the meaning of `trylock` for `Base.ReentrantLock`.
If the Container (or Resource) is not locked, locks it and return the lock event.
Returns `false` if the Container is locked, similarly to the meaning of `trylock` for `Base.ReentrantLock`.
If the capacity of the Container is greater than 1,
multiple requests can be made before blocking occurs.
```jldoctest
julia> sim = Simulation(); res = Resource(sim);
julia> ev = trylock(res)
julia> ev = tryrequest(res)
ConcurrentSim.Put 1
julia> typeof(ev)
ConcurrentSim.Put
julia> trylock(res)
julia> tryrequest(res)
false
```
"""
function trylock(res::Container; priority::Int=0)
function tryrequest(res::Container; priority::Int=0)
islocked(res) && return false # TODO check priority
lock(res; priority)
request(res; priority)
end

function get(con::Container{N}, amount::N; priority::Int=0) where N<:Real
Expand Down Expand Up @@ -114,7 +119,7 @@ Returns `true` if the Container is not empty, similarly to the meaning of `isrea
julia> sim = Simulation(); res = Resource(sim); isready(res)
false
julia> lock(res); isready(res)
julia> request(res); isready(res)
true
```
"""
Expand All @@ -129,13 +134,15 @@ Returns `true` if the store is full, similarly to the meaning of `islocked` for
julia> sim = Simulation(); res = Resource(sim, 2); islocked(res)
false
julia> lock(res); islocked(res)
julia> request(res); islocked(res)
false
julia> lock(res); islocked(res)
julia> request(res); islocked(res)
true
```
"""
islocked(c::Container) = c.level==c.capacity

take!(::Container, args...) = error("There is no well defined `take!` for `Container`. Instead of attempting `take!` consider using `unlock(::Container)` or use a `Store` instead of a `Resource` or `Container`. Think of `Resource` and `Container` as locks and of `Store` as channels. They block only if empty (on taking) or full (on storing).")
lock(::Container) = error("Directly locking a `Container` is not implemented yet. Instead of attempting `lock`, consider using `@yield request(::Container)` from inside of a resumable function.")
trylock(::Container) = error("Directly locking a `Container` is not implemented yet. Instead of attempting `lock`, consider using `@yield request(::Container)` from inside of a resumable function.")
2 changes: 2 additions & 0 deletions src/resources/stores.jl
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ islocked(sto::Store) = sto.load==sto.capacity
unlock(::Store) = error("There is no well defined way to \"unlock\" a Store without taking an element out of it. Instead of attempting `unlock` consider using `take!(::Store)` or use a `Resource` instead of a `Store`. Think of `Resource` and `Container` as locks and of `Store` as channels. They block only if empty (on taking) or full (on storing).")
lock(::Store) = error("There is no well defined way to \"lock\" a Store without storing an element in it. Instead of attempting `lock` consider using `put!(::Store, ...)` or use a `Resource` instead of a `Store`. Think of `Resource` and `Container` as locks and of `Store` as channels. They block only if empty (on taking) or full (on storing).")
trylock(::Store) = error("There is no well defined way to \"lock\" a Store without storing an element in it. Instead of attempting `lock` consider using `put!(::Store, ...)` or use a `Resource` instead of a `Store`. Think of `Resource` and `Container` as locks and of `Store` as channels. They block only if empty (on taking) or full (on storing).")
request(::Store) = error("There is no well defined way to \"request\" a Store without storing an element in it. Instead of attempting `request` consider using `put!(::Store, ...)` or use a `Resource` instead of a `Store`. Think of `Resource` and `Container` as locks and of `Store` as channels. They block only if empty (on taking) or full (on storing).")
tryrequest(::Store) = error("There is no well defined way to \"request\" a Store without storing an element in it. Instead of attempting `request` consider using `put!(::Store, ...)` or use a `Resource` instead of a `Store`. Think of `Resource` and `Container` as locks and of `Store` as channels. They block only if empty (on taking) or full (on storing).")

"""
take!(::Store)
Expand Down
39 changes: 38 additions & 1 deletion test/test_resources_containers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ using ResumableFunctions

@resumable function client(sim::Simulation, res::Resource, i::Int, priority::Int)
println("$(now(sim)), client $i is waiting")
@yield lock(res, priority=priority)
@yield request(res, priority=priority)
println("$(now(sim)), client $i is being served")
@yield timeout(sim, rand())
println("$(now(sim)), client $i has been served")
Expand All @@ -23,6 +23,41 @@ println(res)
@process generate(sim, res)
run(sim)

##

@resumable function client_tryrequest(sim::Simulation, res::Resource, i::Int, priority::Int)
while true
println("$(now(sim)), client $i attempting to request")
attempt = tryrequest(res, priority=priority)
if attempt===false
println("$(now(sim)), client $i is going elsewhere for a bit instead of waiting")
@yield timeout(sim, 0.1)
else
println("$(now(sim)), client $i is being served")
@yield attempt
break
end
end
@yield timeout(sim, rand())
println("$(now(sim)), client $i has been served")
@yield unlock(res)
end

@resumable function generate(sim::Simulation, res::Resource)
for i in 1:10
@process client_tryrequest(sim, res, i, 10-i)
@yield timeout(sim, 0.5*rand())
end
end

sim = Simulation()
res = Resource(sim, 3; level=1)
println(res)
@process generate(sim, res)
run(sim)

##

@resumable function my_consumer(sim::Simulation, con::Container)
for i in 1:10
amount = 3*rand()
Expand Down Expand Up @@ -61,3 +96,5 @@ con = Container(sim, 10.0; level=5.0)
run(sim)

@test_throws ErrorException take!(con)
@test_throws ErrorException lock(con)
@test_throws ErrorException trylock(con)
2 changes: 2 additions & 0 deletions test/test_resources_stores.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,7 @@ run(sim)
##

@test_throws ErrorException unlock(sto)
@test_throws ErrorException request(sto)
@test_throws ErrorException tryrequest(sto)
@test_throws ErrorException lock(sto)
@test_throws ErrorException trylock(sto)

0 comments on commit 718f1db

Please sign in to comment.