Writing a “Counter” actor

Our first example is a simple counter.

While developing, it is useful to have open a Process Browser, so that you can see which actor processes are running, and manually terminate them if necessary.
Process Browser  
Open a process browser by choosing “open…”, then “process browser” from the World menu. Don’t forget to enable auto-updating by right-clicking in the Process Browser’s left-hand pane and selecting “turn on auto-update”.

Defining our behavior class

ActorBehavior subclass: #CounterActor
    instanceVariableNames: 'count'
    classVariableNames: ''
    poolDictionaries: ''
    category: 'ActorExamples'

We define an initialize method…

initialize
    super initialize.
    count := 0.

… and count accessor, just as if this behavior object were a normal Smalltalk object.

count
    ^ count.

Constructing an instance

In a workspace, evaluate

a := CounterActor spawn. "an ActorProxy for an Actor (10138) on nil"
a count. "a Promise"

The result of the second expression is a promise of an eventual answer.

We can wait for the promise to resolve with wait.

a count wait. "0"

Alternatively, we can use the blocking convenience method to arrange for wait to be called for us:

a blocking count. "0"

Counting

Add the following two methods to CounterActor.

add: aNumber
    count := count + aNumber.
    ^ count
increment
    ^ self add: 1

Now, in the workspace,

a blocking add: 123. "123"
a blocking add: 123. "246"
a blocking add: 123. "369"

We can also send a one-way, fire-and-forget asynchronous request to the actor:

a async add: 123. "nil"

And we can increment its count by one:

a increment wait. "493"

Notice that increment is implemented in terms of self add:. In a behavior object, self refers to the object itself, not the current actor. Use Actor me to refer to the current actor.

Tracing activity

Evaluate

a actor tracer: ActorEventStreamTracer forTranscript.

Open a Transcript window.

Now, when we interact with the actor, we will see information about which messages are sent where in both the Transcript and on Squeak’s standard error stream.

For example, evaluating

a async increment.
(a add: 123) wait.
a blocking count.

might yield the following trace in the Transcript:

2018-02-17 19:54:17 (82053) traceEnqueuedMessage: an asynchronous ActorRequest from 54210 (increment)
2018-02-17 19:54:17 (82053) traceEnqueuedMessage: a synchronous ActorRequest from 54210 (add: 123)
2018-02-17 19:54:17 (82053) traceDeliveredMessage: an asynchronous ActorRequest from 54210 (increment)
2018-02-17 19:54:17 (82053) traceHandleRequest: an asynchronous ActorRequest from 54210 (increment)
2018-02-17 19:54:17 (82053) traceDeliveredMessage: a synchronous ActorRequest from 54210 (add: 123)
2018-02-17 19:54:17 (82053) traceHandleRequest: a synchronous ActorRequest from 54210 (add: 123)
2018-02-17 19:54:17 (82053) traceEnqueuedMessage: a synchronous ActorRequest from 54210 (count)
2018-02-17 19:54:17 (82053) traceDeliveredMessage: a synchronous ActorRequest from 54210 (count)
2018-02-17 19:54:17 (82053) traceHandleRequest: a synchronous ActorRequest from 54210 (count)

Each actor can have its own ActorEventTracer, and if it spawns other actors, they inherit its tracer. If no special tracer is assigned to an actor, it uses the system-wide default.

Terminating an actor

Our variable a holds an instance of ActorProxy referring to an underlying Actor, which in turn holds a reference to an instance of CounterActor.

If we want to terminate our actor, we must call terminate or kill on the Actor.

a actor kill. "an Actor (82053) on a CounterActor (terminated)"

Since we earlier configured a tracer, the event is logged to the Transcript:

2018-02-17 19:57:44 (82053) traceActorStopped: Error: Killed

And now our actor has become inert. Making requests of it will yield BrokenPromise exceptions:

p := a count.
p isKindOf: Promise. "true"
p isResolved. "false"
p isRejected. "true"
p error. "an ActorTerminated for an Actor (82053) on a CounterActor (terminated) with Error: Killed"

Even the rejected call to count generates a Transcript message:

2018-02-17 20:00:32 (54210) traceRejectedMessage: a synchronous ActorRequest from 54210 (count)

If we synchronously wait for the result of a Promise resulting from a call to a terminated actor, a BrokenPromise exception will be signaled.

a count wait.     "signals BrokenPromise"
a blocking count. "signals BrokenPromise"

BrokenPromise: Promise was rejected