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.
This commit is contained in:
4 files changed
+511
No files matched your search
@@ -0,0 +1,101 @@
|
|||||||
|
package actor_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/actor"
|
||||||
|
"github.com/lightningnetwork/lnd/fn/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BasicGreetingMsg is a simple message type for the basic actor example.
|
||||||
|
type BasicGreetingMsg struct {
|
||||||
|
actor.BaseMessage
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageType implements actor.Message.
|
||||||
|
func (m BasicGreetingMsg) MessageType() string { return "BasicGreetingMsg" }
|
||||||
|
|
||||||
|
// BasicGreetingResponse is a simple response type.
|
||||||
|
type BasicGreetingResponse struct {
|
||||||
|
Greeting string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExampleActor demonstrates creating a single actor, sending it a message
|
||||||
|
// directly using Ask, and then unregistering and stopping it.
|
||||||
|
func ExampleActor() {
|
||||||
|
system := actor.NewActorSystem()
|
||||||
|
defer system.Shutdown()
|
||||||
|
|
||||||
|
//nolint:ll
|
||||||
|
greeterKey := actor.NewServiceKey[BasicGreetingMsg, BasicGreetingResponse](
|
||||||
|
"basic-greeter",
|
||||||
|
)
|
||||||
|
|
||||||
|
actorID := "my-greeter"
|
||||||
|
greeterBehavior := actor.NewFunctionBehavior(
|
||||||
|
func(ctx context.Context,
|
||||||
|
msg BasicGreetingMsg) fn.Result[BasicGreetingResponse] {
|
||||||
|
|
||||||
|
return fn.Ok(BasicGreetingResponse{
|
||||||
|
Greeting: "Hello, " + msg.Name + " from " +
|
||||||
|
actorID,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Spawn the actor. This registers it with the system and receptionist,
|
||||||
|
// and starts it. It returns an ActorRef.
|
||||||
|
greeterRef, err := greeterKey.Spawn(system, actorID, greeterBehavior)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to spawn actor: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("Actor %s spawned.\n", greeterRef.ID())
|
||||||
|
|
||||||
|
// Send a message directly to the actor's reference.
|
||||||
|
askCtx, askCancel := context.WithTimeout(
|
||||||
|
context.Background(), 1*time.Second,
|
||||||
|
)
|
||||||
|
defer askCancel()
|
||||||
|
futureResponse := greeterRef.Ask(
|
||||||
|
askCtx, BasicGreetingMsg{Name: "World"},
|
||||||
|
)
|
||||||
|
|
||||||
|
awaitCtx, awaitCancel := context.WithTimeout(
|
||||||
|
context.Background(), 1*time.Second,
|
||||||
|
)
|
||||||
|
defer awaitCancel()
|
||||||
|
result := futureResponse.Await(awaitCtx)
|
||||||
|
|
||||||
|
result.WhenErr(func(err error) {
|
||||||
|
fmt.Printf("Error awaiting response: %v\n", err)
|
||||||
|
})
|
||||||
|
result.WhenOk(func(response BasicGreetingResponse) {
|
||||||
|
fmt.Printf("Received: %s\n", response.Greeting)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Unregister the actor. This also stops the actor.
|
||||||
|
unregistered := greeterKey.Unregister(system, greeterRef)
|
||||||
|
if unregistered {
|
||||||
|
fmt.Printf("Actor %s unregistered and stopped.\n",
|
||||||
|
greeterRef.ID())
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Failed to unregister actor %s.\n", greeterRef.ID())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify it's no longer in the receptionist.
|
||||||
|
refsAfterUnregister := actor.FindInReceptionist(
|
||||||
|
system.Receptionist(), greeterKey,
|
||||||
|
)
|
||||||
|
fmt.Printf("Actors for key '%s' after unregister: %d\n",
|
||||||
|
"basic-greeter", len(refsAfterUnregister))
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Actor my-greeter spawned.
|
||||||
|
// Received: Hello, World from my-greeter
|
||||||
|
// Actor my-greeter unregistered and stopped.
|
||||||
|
// Actors for key 'basic-greeter' after unregister: 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
package actor_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/actor"
|
||||||
|
"github.com/lightningnetwork/lnd/fn/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RouterGreetingMsg is a message type for the router example.
|
||||||
|
type RouterGreetingMsg struct {
|
||||||
|
actor.BaseMessage
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageType implements actor.Message.
|
||||||
|
func (m RouterGreetingMsg) MessageType() string { return "RouterGreetingMsg" }
|
||||||
|
|
||||||
|
// RouterGreetingResponse is a response type for the router example.
|
||||||
|
type RouterGreetingResponse struct {
|
||||||
|
Greeting string
|
||||||
|
HandlerID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExampleRouter demonstrates creating multiple actors under the same service
|
||||||
|
// key and using a router to dispatch messages to them.
|
||||||
|
func ExampleRouter() {
|
||||||
|
system := actor.NewActorSystem()
|
||||||
|
defer system.Shutdown()
|
||||||
|
|
||||||
|
//nolint:ll
|
||||||
|
routerGreeterKey := actor.NewServiceKey[RouterGreetingMsg, RouterGreetingResponse](
|
||||||
|
"router-greeter-service",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Behavior for the first greeter actor.
|
||||||
|
actorID1 := "router-greeter-1"
|
||||||
|
greeterBehavior1 := actor.NewFunctionBehavior(
|
||||||
|
func(ctx context.Context,
|
||||||
|
msg RouterGreetingMsg) fn.Result[RouterGreetingResponse] {
|
||||||
|
|
||||||
|
return fn.Ok(RouterGreetingResponse{
|
||||||
|
Greeting: "Greetings, " + msg.Name + "!",
|
||||||
|
HandlerID: actorID1,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
_, err := routerGreeterKey.Spawn(system, actorID1, greeterBehavior1)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to spawn actor: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("Actor %s spawned.\n", actorID1)
|
||||||
|
|
||||||
|
// Behavior for the second greeter actor.
|
||||||
|
actorID2 := "router-greeter-2"
|
||||||
|
greeterBehavior2 := actor.NewFunctionBehavior(
|
||||||
|
func(ctx context.Context,
|
||||||
|
msg RouterGreetingMsg) fn.Result[RouterGreetingResponse] {
|
||||||
|
|
||||||
|
return fn.Ok(RouterGreetingResponse{
|
||||||
|
Greeting: "Salutations, " + msg.Name + "!",
|
||||||
|
HandlerID: actorID2,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
_, err = routerGreeterKey.Spawn(system, actorID2, greeterBehavior2)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to spawn actor: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("Actor %s spawned.\n", actorID2)
|
||||||
|
|
||||||
|
// Create a router for the "router-greeter-service".
|
||||||
|
greeterRouter := actor.NewRouter(
|
||||||
|
system.Receptionist(), routerGreeterKey,
|
||||||
|
actor.NewRoundRobinStrategy[RouterGreetingMsg,
|
||||||
|
RouterGreetingResponse](),
|
||||||
|
system.DeadLetters(),
|
||||||
|
)
|
||||||
|
fmt.Printf("Router %s created for service key '%s'.\n",
|
||||||
|
greeterRouter.ID(), "router-greeter-service")
|
||||||
|
|
||||||
|
// Send messages through the router.
|
||||||
|
names := []string{"Alice", "Bob", "Charlie", "David"}
|
||||||
|
for _, name := range names {
|
||||||
|
askCtx, askCancel := context.WithTimeout(
|
||||||
|
context.Background(), 1*time.Second,
|
||||||
|
)
|
||||||
|
futureResponse := greeterRouter.Ask(
|
||||||
|
askCtx, RouterGreetingMsg{Name: name},
|
||||||
|
)
|
||||||
|
|
||||||
|
awaitCtx, awaitCancel := context.WithTimeout(
|
||||||
|
context.Background(), 1*time.Second,
|
||||||
|
)
|
||||||
|
result := futureResponse.Await(awaitCtx)
|
||||||
|
|
||||||
|
result.WhenErr(func(err error) {
|
||||||
|
fmt.Printf("For %s: Error - %v\n", name, err)
|
||||||
|
})
|
||||||
|
result.WhenOk(func(response RouterGreetingResponse) {
|
||||||
|
fmt.Printf("For %s: Received '%s' from %s\n",
|
||||||
|
name, response.Greeting, response.HandlerID)
|
||||||
|
})
|
||||||
|
awaitCancel()
|
||||||
|
askCancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Actor router-greeter-1 spawned.
|
||||||
|
// Actor router-greeter-2 spawned.
|
||||||
|
// Router router(router-greeter-service) created for service key 'router-greeter-service'.
|
||||||
|
// For Alice: Received 'Greetings, Alice!' from router-greeter-1
|
||||||
|
// For Bob: Received 'Salutations, Bob!' from router-greeter-2
|
||||||
|
// For Charlie: Received 'Greetings, Charlie!' from router-greeter-1
|
||||||
|
// For David: Received 'Salutations, David!' from router-greeter-2
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
package actor_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/actor"
|
||||||
|
"github.com/lightningnetwork/lnd/fn/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CounterMsg is a message type for the stateful counter actor.
|
||||||
|
// It can be used to increment the counter or get its current value.
|
||||||
|
type CounterMsg struct {
|
||||||
|
actor.BaseMessage
|
||||||
|
Increment int
|
||||||
|
GetValue bool
|
||||||
|
Who string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageType implements actor.Message.
|
||||||
|
func (m CounterMsg) MessageType() string { return "CounterMsg" }
|
||||||
|
|
||||||
|
// CounterResponse is a response type for the counter actor.
|
||||||
|
type CounterResponse struct {
|
||||||
|
Value int
|
||||||
|
Responder string
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatefulCounterActor demonstrates an actor that maintains internal state (a
|
||||||
|
// counter) and processes messages to modify or query that state.
|
||||||
|
type StatefulCounterActor struct {
|
||||||
|
counter int
|
||||||
|
actorID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStatefulCounterActor creates a new counter actor.
|
||||||
|
func NewStatefulCounterActor(id string) *StatefulCounterActor {
|
||||||
|
return &StatefulCounterActor{
|
||||||
|
actorID: id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive is the message handler for the StatefulCounterActor.
|
||||||
|
// It implements the actor.ActorBehavior interface implicitly when wrapped.
|
||||||
|
func (s *StatefulCounterActor) Receive(ctx context.Context,
|
||||||
|
msg CounterMsg) fn.Result[CounterResponse] {
|
||||||
|
|
||||||
|
if msg.Increment > 0 {
|
||||||
|
// For increment, we can just acknowledge or return the new
|
||||||
|
// value. Messages are sent serially, so we don't need to worry
|
||||||
|
// about a mutex here.
|
||||||
|
s.counter += msg.Increment
|
||||||
|
|
||||||
|
return fn.Ok(CounterResponse{
|
||||||
|
Value: s.counter,
|
||||||
|
Responder: s.actorID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.GetValue {
|
||||||
|
return fn.Ok(CounterResponse{
|
||||||
|
Value: s.counter,
|
||||||
|
Responder: s.actorID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn.Err[CounterResponse](fmt.Errorf("invalid CounterMsg"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExampleActor_stateful demonstrates creating an actor whose behavior is defined
|
||||||
|
// by a struct with methods, allowing it to maintain internal state.
|
||||||
|
func ExampleActor_stateful() {
|
||||||
|
system := actor.NewActorSystem()
|
||||||
|
defer system.Shutdown()
|
||||||
|
|
||||||
|
counterServiceKey := actor.NewServiceKey[CounterMsg, CounterResponse](
|
||||||
|
"struct-counter-service",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create an instance of our stateful actor logic.
|
||||||
|
actorID := "counter-actor-1"
|
||||||
|
counterLogic := NewStatefulCounterActor(actorID)
|
||||||
|
|
||||||
|
// Spawn the actor.
|
||||||
|
// The counterLogic instance itself satisfies the ActorBehavior
|
||||||
|
// interface because its Receive method matches the required signature.
|
||||||
|
counterRef, err := counterServiceKey.Spawn(
|
||||||
|
system, actorID, counterLogic,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to spawn actor: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("Actor %s spawned.\n", counterRef.ID())
|
||||||
|
|
||||||
|
// Send messages to increment the counter.
|
||||||
|
for i := 1; i <= 3; i++ {
|
||||||
|
askCtx, askCancel := context.WithTimeout(
|
||||||
|
context.Background(), 1*time.Second,
|
||||||
|
)
|
||||||
|
futureResp := counterRef.Ask(askCtx,
|
||||||
|
CounterMsg{
|
||||||
|
Increment: i,
|
||||||
|
Who: fmt.Sprintf("Incrementer-%d", i),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
awaitCtx, awaitCancel := context.WithTimeout(
|
||||||
|
context.Background(), 1*time.Second,
|
||||||
|
)
|
||||||
|
resp := futureResp.Await(awaitCtx)
|
||||||
|
|
||||||
|
resp.WhenOk(func(r CounterResponse) {
|
||||||
|
fmt.Printf("Incremented by %d, new value: %d "+
|
||||||
|
"(from %s)\n", i, r.Value, r.Responder)
|
||||||
|
})
|
||||||
|
resp.WhenErr(func(e error) {
|
||||||
|
fmt.Printf("Error incrementing: %v\n", e)
|
||||||
|
})
|
||||||
|
awaitCancel()
|
||||||
|
askCancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a message to get the current value.
|
||||||
|
askCtx, askCancel := context.WithTimeout(
|
||||||
|
context.Background(), 1*time.Second,
|
||||||
|
)
|
||||||
|
futureResp := counterRef.Ask(
|
||||||
|
askCtx, CounterMsg{GetValue: true, Who: "Getter"},
|
||||||
|
)
|
||||||
|
|
||||||
|
awaitCtx, awaitCancel := context.WithTimeout(
|
||||||
|
context.Background(), 1*time.Second,
|
||||||
|
)
|
||||||
|
|
||||||
|
finalValueResp := futureResp.Await(awaitCtx)
|
||||||
|
finalValueResp.WhenOk(func(r CounterResponse) {
|
||||||
|
fmt.Printf("Final counter value: %d (from %s)\n",
|
||||||
|
r.Value, r.Responder)
|
||||||
|
})
|
||||||
|
finalValueResp.WhenErr(func(e error) {
|
||||||
|
fmt.Printf("Error getting value: %v\n", e)
|
||||||
|
})
|
||||||
|
awaitCancel()
|
||||||
|
askCancel()
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Actor counter-actor-1 spawned.
|
||||||
|
// Incremented by 1, new value: 1 (from counter-actor-1)
|
||||||
|
// Incremented by 2, new value: 3 (from counter-actor-1)
|
||||||
|
// Incremented by 3, new value: 6 (from counter-actor-1)
|
||||||
|
// Final counter value: 6 (from counter-actor-1)
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
package actor_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/actor"
|
||||||
|
"github.com/lightningnetwork/lnd/fn/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LogMsg is a message type for the TellOnly example.
|
||||||
|
type LogMsg struct {
|
||||||
|
actor.BaseMessage
|
||||||
|
Text string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageType implements actor.Message.
|
||||||
|
func (m LogMsg) MessageType() string { return "LogMsg" }
|
||||||
|
|
||||||
|
// LoggerActorBehavior is a simple actor behavior that logs messages. It doesn't
|
||||||
|
// produce a meaningful response for Ask, so it's a good candidate for TellOnly
|
||||||
|
// interactions.
|
||||||
|
type LoggerActorBehavior struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
logs []string
|
||||||
|
actorID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLoggerActorBehavior(id string) *LoggerActorBehavior {
|
||||||
|
return &LoggerActorBehavior{actorID: id}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive processes LogMsg messages by appending them to an internal log. The
|
||||||
|
// response type is 'any' as it's not typically used with Ask.
|
||||||
|
func (l *LoggerActorBehavior) Receive(ctx context.Context,
|
||||||
|
msg actor.Message) fn.Result[any] {
|
||||||
|
|
||||||
|
logMessage, ok := msg.(LogMsg)
|
||||||
|
if !ok {
|
||||||
|
return fn.Err[any](fmt.Errorf("unexpected message "+
|
||||||
|
"type: %s", msg.MessageType()))
|
||||||
|
}
|
||||||
|
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
|
||||||
|
entry := fmt.Sprintf("[%s from %s]: %s", time.Now().Format("15:04:05"),
|
||||||
|
l.actorID, logMessage.Text)
|
||||||
|
l.logs = append(l.logs, entry)
|
||||||
|
|
||||||
|
// For Tell, the result is often ignored, but we must return something.
|
||||||
|
return fn.Ok[any](nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LoggerActorBehavior) GetLogs() []string {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
|
||||||
|
copiedLogs := make([]string, len(l.logs))
|
||||||
|
copy(copiedLogs, l.logs)
|
||||||
|
|
||||||
|
return copiedLogs
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExampleTellOnlyRef demonstrates using a TellOnlyRef for fire-and-forget
|
||||||
|
// messaging with an actor.
|
||||||
|
func ExampleTellOnlyRef() {
|
||||||
|
system := actor.NewActorSystem()
|
||||||
|
defer system.Shutdown()
|
||||||
|
|
||||||
|
// The logger actor doesn't really have a response type for Ask, so we
|
||||||
|
// use 'any'.
|
||||||
|
loggerServiceKey := actor.NewServiceKey[actor.Message, any](
|
||||||
|
"tell-only-logger-service",
|
||||||
|
)
|
||||||
|
|
||||||
|
actorID := "my-logger"
|
||||||
|
loggerLogic := NewLoggerActorBehavior(actorID)
|
||||||
|
|
||||||
|
// Spawn the actor.
|
||||||
|
fullRef, err := loggerServiceKey.Spawn(system, actorID, loggerLogic)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to spawn actor: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("Actor %s spawned.\n", fullRef.ID())
|
||||||
|
|
||||||
|
// Get a TellOnlyRef for the actor. We can get this from the Actor
|
||||||
|
// instance itself if we had it, or by type assertion if we know the
|
||||||
|
// underlying ref supports it. Since fullRef is ActorRef[actor.Message,
|
||||||
|
// any], it already satisfies TellOnlyRef[actor.Message].
|
||||||
|
//
|
||||||
|
// Or, if we had the *Actor instance: tellOnlyLogger =
|
||||||
|
// actorInstance.TellRef()
|
||||||
|
var tellOnlyLogger actor.TellOnlyRef[actor.Message] = fullRef
|
||||||
|
|
||||||
|
fmt.Printf("Obtained TellOnlyRef for %s.\n", tellOnlyLogger.ID())
|
||||||
|
|
||||||
|
// Send messages using Tell.
|
||||||
|
tellOnlyLogger.Tell(
|
||||||
|
context.Background(), LogMsg{Text: "First log entry."},
|
||||||
|
)
|
||||||
|
tellOnlyLogger.Tell(
|
||||||
|
context.Background(), LogMsg{Text: "Second log entry."},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Allow some time for messages to be processed.
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
|
||||||
|
// Retrieve logs directly from the behavior for verification in this
|
||||||
|
// example. In a real scenario, this might not be possible or desired.
|
||||||
|
logs := loggerLogic.GetLogs()
|
||||||
|
fmt.Println("Logged entries:")
|
||||||
|
for _, entry := range logs {
|
||||||
|
// Strip the timestamp and actor ID for consistent example
|
||||||
|
// output. Example entry: "[15:04:05 from my-logger]: Actual log
|
||||||
|
// text"
|
||||||
|
parts := strings.SplitN(entry, "]: ", 2)
|
||||||
|
if len(parts) == 2 {
|
||||||
|
fmt.Println(parts[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempting to Ask using tellOnlyLogger would be a compile-time error:
|
||||||
|
// tellOnlyLogger.Ask(context.Background(), LogMsg{Text: "This would
|
||||||
|
// fail"})
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Actor my-logger spawned.
|
||||||
|
// Obtained TellOnlyRef for my-logger.
|
||||||
|
// Logged entries:
|
||||||
|
// First log entry.
|
||||||
|
// Second log entry.
|
||||||
|
}
|
||||||
Reference in new issue
Block a user