Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
pbayer committed Dec 10, 2020
2 parents 4d0d07e + f5fad55 commit 1e8b3b9
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 13 deletions.
23 changes: 22 additions & 1 deletion docs/src/behaviors.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ bhv(1, 1) # execute it with communication parameters

## Object-oriented Style

Alternatively we put the acquaintance parameters in an object which we can make executable (a [functor](https://en.wikipedia.org/wiki/Function_object)) with communication parameters:
Alternatively we put the acquaintance parameters in an object which we make executable (e.g. a [functor](https://en.wikipedia.org/wiki/Function_object)) with communication parameters:

```@repl
struct Acqu # define an object with acquaintances
Expand All @@ -56,6 +56,23 @@ bhv = Acqu(1,1,2,2) # create an instance
bhv(1,1) # execute it with communication parameters
```

## Freestyle

With being callable the only hard requirement for a behavior, you can pass anything callable as behavior to an actor regardless whether it contains acquaintances or not:

```@repl
using Actors, .Threads
import Actors: spawn, newLink
myactor = spawn(threadid) # a parameterless function
call(myactor)
become!(myactor, (lk, x, y) -> send(lk, x^y)) # an anonymous function with communication arguments
me = newLink()
send(myactor, me, 123, 456)
receive(me)
```

Of course you can give objects containing acquaintances as parameters to a function and create a partial application with `Bhv` on them and much more. Be my guest!

## Agha's Stack example

Now more realistically for actor behavior we reproduce Agha's example 3.2.1 [^3]:
Expand Down Expand Up @@ -119,6 +136,10 @@ julia> for i ∈ 1:5

An actor's behavior is set with [`spawn`](@ref) and gets changed with [`become!`](@ref). Inside a behavior function an actor can change its own behavior with [`become`](@ref). In both cases a callable object together with acquaintance arguments can be specified as new behavior. This is effective when the next message gets processed.

## Be Careful with Mutable Variables

As you have seen, you are very free in how you define behaviors, but you must be very careful in passing mutable variables as acquaintances to actors as they could be accessed by other actors on other threads concurrently causing race conditions. If that's the case, you can wrap mutable variables into a [`:guard`](https://github.com/JuliaActors/Guards.jl) actor, which will manage access to them.

[^1]: see the [Actor Model](https://en.wikipedia.org/wiki/Actor_model#Behaviors) on Wikipedia.
[^2]: Gul Agha 1986. *Actors. a model of concurrent computation in distributed systems*, MIT.- p. 30
[^3]: ibid. p. 34
6 changes: 5 additions & 1 deletion docs/src/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Since Gul Agha's actor glossary [^1] is very useful, we include here some of his
| **actor** | A computational agent which has an mail address and a behavior. Actors communicate by message-passing and carry out their actions concurrently. |
| **asynchronous communication** | Communication is considered to be asynchronous when the sender does not have to wait for the recipient to be ready to accept a communication before the sender can send the communication. |
| **behavior** | The behavior of an actor maps the incoming communication to a three tuple of tasks created, new actors created, and the replacement behavior. |
| **behaviour** | "A behaviour encapsulates common behavioural patterns." Armstrong [^2] The Erlang world has a more complex view of behaviors. Such more complex behaviors can be realized with a message protocol. |
| **communication** | The only mechanism by which actors may affect each other's behavior. The content of a message sent by an actor is called a communication. |
| **concurrency** | The potentially parallel execution of actions without a determinate predefined sequence for their actions. |
| **customer** | A request communication contains the mail address of an actor called the customer to which a reply to the request is to be sent. Customers are dynamically created to carry out the rest of the computation, so that an actor sending a request to another actor can begin processing the next incoming communication without waiting for the subcomputations of the previous communication to complete. |
Expand All @@ -16,10 +17,13 @@ Since Gul Agha's actor glossary [^1] is very useful, we include here some of his
| **future** | A future is an actor representing the value of a computation in progress. Futures can speed up computation since they allow subcomputations using references to a value to proceed concurrently with the evaluation of an expression to compute the value. Communications sent to a future are queued until the value has been determined. |
| **mail address** | A virtual location by which an actor may be accessed. Each actor has a unique mail address which is invariant, although the behavior of an actor may change over time. |
| **mail queue** | The queue of incoming communications sent to a given actor. The mail queue represents the arrival order of communications and provides the means to buffer communications until they are processed by the target actor. |
| **protocol** | An actor follows a protocol if it – when receiving certain messages – executes predefined behaviors other than its current behavior. This is used to implement more complex behaviours and goes beyond the classical model. |
| **receptionist** | An actor to whom communications may be sent from outside the configuration to which it belongs. The set of receptionists evolves dynamically as the mail addresses of various actors may be communicated to actors outside the system. |
| **replacement behavior** | A behavior specified by an actor processing a communication which is used to process the next communication in the mail queue of the actor. |
| **reply** | A communication sent in response to a request (see also customers). |
| **request** | A communication asking for a response to be sent to a customer contained in the request. |
| **synchronous communication** | Communication between two actors requiring the sender to wait until the recipient acknowledges or otherwise responds to the communication before continuing with further processing. Synchronous communication in actors is implemented using customers. |

[^1]: in: Gul Agha 1986. *Actors. a model of concurrent computation in distributed systems*, MIT; Appendix B
[^1]: Gul Agha 1986. *Actors. a model of concurrent computation in distributed systems*, MIT; Appendix B
[^2]: Joe Armstrong 2014. Programming Erlang, 2nd Ed.,
Software for a Concurrent World, Pragmatic Programmers; p. 361
3 changes: 2 additions & 1 deletion docs/src/infrastructure.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ Actor tasks execute one computation, mostly without communicating with other act

You can start actor tasks with [`async`](@ref) and get their result with [`await`](@ref).

```@repl actors
```@repl
using Actors
t = async(Bhv(^, 123, 456));
await(t)
```
Expand Down
14 changes: 9 additions & 5 deletions src/actor.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Actor libraries or applications can use this to
- plugin the `Actors.jl` API (first form) or
- extend it to other protocols by using the 2nd form.
"""
Classic.onmessage(A::_ACT, msg) = (A.res = A.bhv(msg...))
Classic.onmessage(A::_ACT, msg) = (A.res = Base.invokelatest(A.bhv, msg...))

#
# the 2nd actor layer is realized by the Msg protocol
Expand Down Expand Up @@ -134,23 +134,27 @@ Classic.self() = task_local_storage("_ACT").self
"""
```
become(bhv)
become(func, args...; kwargs...)
become(func::F, args...; kwargs...) where F<:Function
```
Cause your actor to take on a new behavior. This can only be
called from inside an actor/behavior.
# Arguments
- `bhv`: [`Bhv`](@ref) implementing the new behavior,
- `func`: callable object,
- `bhv`: a callable object implementing the new behavior,
- `func::F`: callable object,
- `args1...`: (partial) arguments to `func`,
- `kwargs...`: keyword arguments to `func`.
"""
function Classic.become(bhv)
act = task_local_storage("_ACT")
act.bhv = bhv
end
Classic.become(func, args...; kwargs...) = become(Bhv(func, args...; kwargs...))
function Classic.become(func::F, args...; kwargs...) where F<:Function
isempty(args) && isempty(kwargs) ?
task_local_storage("_ACT").bhv = func :
become(Bhv(func, args...; kwargs...))
end
#
# Note: a reference to the actor's status variable must be
# available as task_local_storage("_ACT") for this to
Expand Down
6 changes: 5 additions & 1 deletion src/api.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ Cause an actor to change behavior.
- `kwargs...`: keyword arguments to `func`.
"""
become!(lk::Link, bhv) = send(lk, Become(bhv))
become!(lk::Link, func::F, args...; kwargs...) where F<:Function = become!(lk, Bhv(func, args...; kwargs...))
function become!(lk::Link, func::F, args...; kwargs...) where F<:Function
isempty(args) && isempty(kwargs) ?
send(lk, Become(func)) :
become!(lk, Bhv(func, args...; kwargs...))
end
become!(name::Symbol, args...; kwargs...) = become!(whereis(name), args...; kwargs...)

"""
Expand Down
5 changes: 3 additions & 2 deletions src/com.jl
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Classic.send(lk::Link, msg::Msg) = _send!(lk.chn, msg)
Classic.send(lk::Link, msg...) = _send!(lk.chn, msg)
Classic.send(name::Symbol, msg...) = _send!(whereis(name).chn, msg...)

_match(msg::Msg, ::Nothing, ::Nothing) = true
_match(msg, ::Nothing, ::Nothing) = true
_match(msg::Msg, M::Type{<:Msg}, ::Nothing) = msg isa M
_match(msg::Msg, ::Nothing, from::Link) =
:from in fieldnames(typeof(msg)) ? msg.from == from : false
Expand Down Expand Up @@ -107,7 +107,8 @@ function receive(lk::L1, M::MT, from::L2;
push!(stash, take!(lk.chn))
end
foreach(x->put!(lk.chn, x), stash)
return msg
return applicable(length, msg) ?
length(msg) == 1 ? first(msg) : msg : msg
end

"""
Expand Down
10 changes: 8 additions & 2 deletions test/test_basics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ a[] = 1

inca(a, b) = a[] = a[] + b

me = newLink()

act = spawn(Bhv(inca, a), taskref=t)

@test t[].state == :runnable
Expand All @@ -25,8 +27,12 @@ sleep(0.1)
@test a[] == 3
become!(act, threadid)
@test request(act) > 1
@test A.bhv == threadid
become!(act, (lk, x, y) -> send(lk, x^y))
send(act, me, 123, 456)
@test receive(me) == 2409344748064316129

act1 = spawn(Bhv(threadid), sticky=true)
act1 = spawn(threadid, sticky=true)
@test request(act1) == 1
act2 = spawn(Bhv(threadid), thrd=2)
act2 = spawn(threadid, thrd=2)
@test request(act2) == 2

0 comments on commit 1e8b3b9

Please sign in to comment.