Skip to content

Commit f6a23b7

Browse files
committed
actor: add example files
In this commit, we add a series of examples that show how the package can be used in the wild. They can be run as normal Example tests.
1 parent ff26d0c commit f6a23b7

File tree

4 files changed

+489
-0
lines changed

4 files changed

+489
-0
lines changed

actor/example_basic_actor_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package actor_test
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
8+
"github.com/lightningnetwork/lnd/actor"
9+
"github.com/lightningnetwork/lnd/fn/v2"
10+
)
11+
12+
// BasicGreetingMsg is a simple message type for the basic actor example.
13+
type BasicGreetingMsg struct {
14+
actor.BaseMessage
15+
Name string
16+
}
17+
18+
// MessageType implements actor.Message.
19+
func (m BasicGreetingMsg) MessageType() string { return "BasicGreetingMsg" }
20+
21+
// BasicGreetingResponse is a simple response type.
22+
type BasicGreetingResponse struct {
23+
Greeting string
24+
}
25+
26+
// ExampleActor demonstrates creating a single actor, sending it a message
27+
// directly using Ask, and then unregistering and stopping it.
28+
func ExampleActor() {
29+
system := actor.NewActorSystem()
30+
defer system.Shutdown()
31+
32+
//nolint:ll
33+
greeterKey := actor.NewServiceKey[BasicGreetingMsg, BasicGreetingResponse](
34+
"basic-greeter",
35+
)
36+
37+
actorID := "my-greeter"
38+
greeterBehavior := actor.NewFunctionBehavior(
39+
func(ctx context.Context,
40+
msg BasicGreetingMsg) fn.Result[BasicGreetingResponse] {
41+
42+
return fn.Ok(BasicGreetingResponse{
43+
Greeting: "Hello, " + msg.Name + " from " +
44+
actorID,
45+
})
46+
},
47+
)
48+
49+
// Spawn the actor. This registers it with the system and receptionist,
50+
// and starts it. It returns an ActorRef.
51+
greeterRef := greeterKey.Spawn(system, actorID, greeterBehavior)
52+
fmt.Printf("Actor %s spawned.\n", greeterRef.ID())
53+
54+
// Send a message directly to the actor's reference.
55+
askCtx, askCancel := context.WithTimeout(
56+
context.Background(), 1*time.Second,
57+
)
58+
defer askCancel()
59+
futureResponse := greeterRef.Ask(
60+
askCtx, BasicGreetingMsg{Name: "World"},
61+
)
62+
63+
awaitCtx, awaitCancel := context.WithTimeout(
64+
context.Background(), 1*time.Second,
65+
)
66+
defer awaitCancel()
67+
result := futureResponse.Await(awaitCtx)
68+
69+
result.WhenErr(func(err error) {
70+
fmt.Printf("Error awaiting response: %v\n", err)
71+
})
72+
result.WhenOk(func(response BasicGreetingResponse) {
73+
fmt.Printf("Received: %s\n", response.Greeting)
74+
})
75+
76+
// Unregister the actor. This also stops the actor.
77+
unregistered := greeterKey.Unregister(system, greeterRef)
78+
if unregistered {
79+
fmt.Printf("Actor %s unregistered and stopped.\n",
80+
greeterRef.ID())
81+
} else {
82+
fmt.Printf("Failed to unregister actor %s.\n", greeterRef.ID())
83+
}
84+
85+
// Verify it's no longer in the receptionist.
86+
refsAfterUnregister := actor.FindInReceptionist(
87+
system.Receptionist(), greeterKey,
88+
)
89+
fmt.Printf("Actors for key '%s' after unregister: %d\n",
90+
"basic-greeter", len(refsAfterUnregister))
91+
92+
// Output:
93+
// Actor my-greeter spawned.
94+
// Received: Hello, World from my-greeter
95+
// Actor my-greeter unregistered and stopped.
96+
// Actors for key 'basic-greeter' after unregister: 0
97+
}

actor/example_router_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package actor_test
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
8+
"github.com/lightningnetwork/lnd/actor"
9+
"github.com/lightningnetwork/lnd/fn/v2"
10+
)
11+
12+
// RouterGreetingMsg is a message type for the router example.
13+
type RouterGreetingMsg struct {
14+
actor.BaseMessage
15+
Name string
16+
}
17+
18+
// MessageType implements actor.Message.
19+
func (m RouterGreetingMsg) MessageType() string { return "RouterGreetingMsg" }
20+
21+
// RouterGreetingResponse is a response type for the router example.
22+
type RouterGreetingResponse struct {
23+
Greeting string
24+
HandlerID string
25+
}
26+
27+
// ExampleRouter demonstrates creating multiple actors under the same service
28+
// key and using a router to dispatch messages to them.
29+
func ExampleRouter() {
30+
system := actor.NewActorSystem()
31+
defer system.Shutdown()
32+
33+
//nolint:ll
34+
routerGreeterKey := actor.NewServiceKey[RouterGreetingMsg, RouterGreetingResponse](
35+
"router-greeter-service",
36+
)
37+
38+
// Behavior for the first greeter actor.
39+
actorID1 := "router-greeter-1"
40+
greeterBehavior1 := actor.NewFunctionBehavior(
41+
func(ctx context.Context,
42+
msg RouterGreetingMsg) fn.Result[RouterGreetingResponse] {
43+
44+
return fn.Ok(RouterGreetingResponse{
45+
Greeting: "Greetings, " + msg.Name + "!",
46+
HandlerID: actorID1,
47+
})
48+
},
49+
)
50+
routerGreeterKey.Spawn(system, actorID1, greeterBehavior1)
51+
fmt.Printf("Actor %s spawned.\n", actorID1)
52+
53+
// Behavior for the second greeter actor.
54+
actorID2 := "router-greeter-2"
55+
greeterBehavior2 := actor.NewFunctionBehavior(
56+
func(ctx context.Context,
57+
msg RouterGreetingMsg) fn.Result[RouterGreetingResponse] {
58+
59+
return fn.Ok(RouterGreetingResponse{
60+
Greeting: "Salutations, " + msg.Name + "!",
61+
HandlerID: actorID2,
62+
})
63+
},
64+
)
65+
routerGreeterKey.Spawn(system, actorID2, greeterBehavior2)
66+
fmt.Printf("Actor %s spawned.\n", actorID2)
67+
68+
// Create a router for the "router-greeter-service".
69+
greeterRouter := actor.NewRouter(
70+
system.Receptionist(), routerGreeterKey,
71+
actor.NewRoundRobinStrategy[RouterGreetingMsg,
72+
RouterGreetingResponse](),
73+
system.DeadLetters(),
74+
)
75+
fmt.Printf("Router %s created for service key '%s'.\n",
76+
greeterRouter.ID(), "router-greeter-service")
77+
78+
// Send messages through the router.
79+
names := []string{"Alice", "Bob", "Charlie", "David"}
80+
for _, name := range names {
81+
askCtx, askCancel := context.WithTimeout(
82+
context.Background(), 1*time.Second,
83+
)
84+
futureResponse := greeterRouter.Ask(
85+
askCtx, RouterGreetingMsg{Name: name},
86+
)
87+
88+
awaitCtx, awaitCancel := context.WithTimeout(
89+
context.Background(), 1*time.Second,
90+
)
91+
result := futureResponse.Await(awaitCtx)
92+
93+
result.WhenErr(func(err error) {
94+
fmt.Printf("For %s: Error - %v\n", name, err)
95+
})
96+
result.WhenOk(func(response RouterGreetingResponse) {
97+
fmt.Printf("For %s: Received '%s' from %s\n",
98+
name, response.Greeting, response.HandlerID)
99+
})
100+
awaitCancel()
101+
askCancel()
102+
}
103+
104+
// Output:
105+
// Actor router-greeter-1 spawned.
106+
// Actor router-greeter-2 spawned.
107+
// Router router(router-greeter-service) created for service key 'router-greeter-service'.
108+
// For Alice: Received 'Greetings, Alice!' from router-greeter-1
109+
// For Bob: Received 'Salutations, Bob!' from router-greeter-2
110+
// For Charlie: Received 'Greetings, Charlie!' from router-greeter-1
111+
// For David: Received 'Salutations, David!' from router-greeter-2
112+
}

actor/example_struct_actor_test.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package actor_test
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
8+
"github.com/lightningnetwork/lnd/actor"
9+
"github.com/lightningnetwork/lnd/fn/v2"
10+
)
11+
12+
// CounterMsg is a message type for the stateful counter actor.
13+
// It can be used to increment the counter or get its current value.
14+
type CounterMsg struct {
15+
actor.BaseMessage
16+
Increment int
17+
GetValue bool
18+
Who string
19+
}
20+
21+
// MessageType implements actor.Message.
22+
func (m CounterMsg) MessageType() string { return "CounterMsg" }
23+
24+
// CounterResponse is a response type for the counter actor.
25+
type CounterResponse struct {
26+
Value int
27+
Responder string
28+
}
29+
30+
// StatefulCounterActor demonstrates an actor that maintains internal state (a
31+
// counter) and processes messages to modify or query that state.
32+
type StatefulCounterActor struct {
33+
counter int
34+
actorID string
35+
}
36+
37+
// NewStatefulCounterActor creates a new counter actor.
38+
func NewStatefulCounterActor(id string) *StatefulCounterActor {
39+
return &StatefulCounterActor{
40+
actorID: id,
41+
}
42+
}
43+
44+
// Receive is the message handler for the StatefulCounterActor.
45+
// It implements the actor.ActorBehavior interface implicitly when wrapped.
46+
func (s *StatefulCounterActor) Receive(ctx context.Context,
47+
msg CounterMsg) fn.Result[CounterResponse] {
48+
49+
if msg.Increment > 0 {
50+
// For increment, we can just acknowledge or return the new
51+
// value. Messages are sent serially, so we don't need to worry
52+
// about a mutex here.
53+
s.counter += msg.Increment
54+
55+
return fn.Ok(CounterResponse{
56+
Value: s.counter,
57+
Responder: s.actorID,
58+
})
59+
}
60+
61+
if msg.GetValue {
62+
return fn.Ok(CounterResponse{
63+
Value: s.counter,
64+
Responder: s.actorID,
65+
})
66+
}
67+
68+
return fn.Err[CounterResponse](fmt.Errorf("invalid CounterMsg"))
69+
}
70+
71+
// ExampleActor_stateful demonstrates creating an actor whose behavior is defined
72+
// by a struct with methods, allowing it to maintain internal state.
73+
func ExampleActor_stateful() {
74+
system := actor.NewActorSystem()
75+
defer system.Shutdown()
76+
77+
counterServiceKey := actor.NewServiceKey[CounterMsg, CounterResponse](
78+
"struct-counter-service",
79+
)
80+
81+
// Create an instance of our stateful actor logic.
82+
actorID := "counter-actor-1"
83+
counterLogic := NewStatefulCounterActor(actorID)
84+
85+
// Spawn the actor.
86+
// The counterLogic instance itself satisfies the ActorBehavior
87+
// interface because its Receive method matches the required signature.
88+
counterRef := counterServiceKey.Spawn(system, actorID, counterLogic)
89+
fmt.Printf("Actor %s spawned.\n", counterRef.ID())
90+
91+
// Send messages to increment the counter.
92+
for i := 1; i <= 3; i++ {
93+
askCtx, askCancel := context.WithTimeout(
94+
context.Background(), 1*time.Second,
95+
)
96+
futureResp := counterRef.Ask(askCtx,
97+
CounterMsg{
98+
Increment: i,
99+
Who: fmt.Sprintf("Incrementer-%d", i),
100+
},
101+
)
102+
awaitCtx, awaitCancel := context.WithTimeout(
103+
context.Background(), 1*time.Second,
104+
)
105+
resp := futureResp.Await(awaitCtx)
106+
107+
resp.WhenOk(func(r CounterResponse) {
108+
fmt.Printf("Incremented by %d, new value: %d "+
109+
"(from %s)\n", i, r.Value, r.Responder)
110+
})
111+
resp.WhenErr(func(e error) {
112+
fmt.Printf("Error incrementing: %v\n", e)
113+
})
114+
awaitCancel()
115+
askCancel()
116+
}
117+
118+
// Send a message to get the current value.
119+
askCtx, askCancel := context.WithTimeout(
120+
context.Background(), 1*time.Second,
121+
)
122+
futureResp := counterRef.Ask(
123+
askCtx, CounterMsg{GetValue: true, Who: "Getter"},
124+
)
125+
126+
awaitCtx, awaitCancel := context.WithTimeout(
127+
context.Background(), 1*time.Second,
128+
)
129+
130+
finalValueResp := futureResp.Await(awaitCtx)
131+
finalValueResp.WhenOk(func(r CounterResponse) {
132+
fmt.Printf("Final counter value: %d (from %s)\n",
133+
r.Value, r.Responder)
134+
})
135+
finalValueResp.WhenErr(func(e error) {
136+
fmt.Printf("Error getting value: %v\n", e)
137+
})
138+
awaitCancel()
139+
askCancel()
140+
141+
// Output:
142+
// Actor counter-actor-1 spawned.
143+
// Incremented by 1, new value: 1 (from counter-actor-1)
144+
// Incremented by 2, new value: 3 (from counter-actor-1)
145+
// Incremented by 3, new value: 6 (from counter-actor-1)
146+
// Final counter value: 6 (from counter-actor-1)
147+
}

0 commit comments

Comments
 (0)