-
Notifications
You must be signed in to change notification settings - Fork 119
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* randomize listen port. makes the tests pass on macos. * wip * make deadletter an actor. * initialization code ok. tests in place. * delete some commented out code. * we stop storing the deadletters. just log them if there is a logger. the user can supply their own dead letter handling if they need to. * since I've change the semantics of the internal registry/get method these two tests needed to change. * registry/get now returns nil instead of deadletter. * a tiny tweak that is more readable. * make a wrapper around bytes.Buffer to suppress the race detector in the tests. * just some docs.
- Loading branch information
Showing
13 changed files
with
231 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,42 @@ | ||
package actor | ||
|
||
import ( | ||
"reflect" | ||
"sync" | ||
|
||
"github.com/anthdm/hollywood/log" | ||
"reflect" | ||
) | ||
|
||
// TODO: The deadLetter is implemented as a plain Processer, but | ||
// can actually be implemented as a Receiver. This is a good first issue. | ||
// | ||
|
||
type deadLetter struct { | ||
eventStream *EventStream | ||
pid *PID | ||
logger log.Logger | ||
logger log.Logger | ||
pid *PID | ||
} | ||
|
||
func newDeadLetter(eventStream *EventStream) *deadLetter { | ||
func newDeadLetter() Receiver { | ||
pid := NewPID(LocalLookupAddr, "deadLetter") | ||
return &deadLetter{ | ||
eventStream: eventStream, | ||
pid: NewPID(LocalLookupAddr, "deadLetter"), | ||
logger: eventStream.logger.SubLogger("[deadLetter]"), | ||
pid: pid, | ||
} | ||
} | ||
|
||
func (d *deadLetter) Send(dest *PID, msg any, sender *PID) { | ||
d.logger.Warnw("Send", | ||
"dest", dest, | ||
"msg", reflect.TypeOf(msg), | ||
"sender", sender, | ||
) | ||
d.eventStream.Publish(&DeadLetterEvent{ | ||
Target: dest, | ||
Message: msg, | ||
Sender: sender, | ||
}) | ||
// Receive implements the Receiver interface, handling the deadletter messages. | ||
// It will log the deadletter message if a logger is set. If not, it will silently | ||
// ignore the message. Any production system should either have a logger set or provide a custom | ||
// deadletter actor. | ||
func (d *deadLetter) Receive(ctx *Context) { | ||
switch msg := ctx.Message().(type) { | ||
case Started: | ||
// intialize logger on deadletter startup. is this a sane approach? I'm not sure how the get to the logger otherwise. | ||
d.logger = ctx.Engine().logger.SubLogger("[deadletter]") | ||
d.logger.Debugw("default deadletter actor started") | ||
case Stopped: | ||
d.logger.Debugw("default deadletter actor stopped") | ||
case Initialized: | ||
d.logger.Debugw("default deadletter actor initialized") | ||
case *DeadLetterEvent: | ||
d.logger.Warnw("deadletter arrived", "msg-type", reflect.TypeOf(msg), | ||
"sender", msg.Sender, "target", msg.Target, "msg", msg.Message) | ||
default: | ||
d.logger.Errorw("unknown message arrived", "msg", msg) | ||
} | ||
} | ||
|
||
func (d *deadLetter) PID() *PID { return d.pid } | ||
func (d *deadLetter) Shutdown(_ *sync.WaitGroup) {} | ||
func (d *deadLetter) Start() {} | ||
func (d *deadLetter) Invoke([]Envelope) {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package actor | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"github.com/anthdm/hollywood/log" | ||
"github.com/stretchr/testify/assert" | ||
"log/slog" | ||
"os" | ||
"sync" | ||
"testing" | ||
"time" | ||
) | ||
|
||
// TestDeadLetterDefault tests the default deadletter handling. | ||
// It will spawn a new actor, kill it, send a message to it and then check if the deadletter | ||
// received the message. | ||
func TestDeadLetterDefault(t *testing.T) { | ||
logBuffer := SafeBuffer{} | ||
lh := log.NewHandler(&logBuffer, log.TextFormat, slog.LevelDebug) | ||
e := NewEngine(EngineOptLogger(log.NewLogger("[engine]", lh))) | ||
a1 := e.Spawn(newTestActor, "a1") | ||
assert.NotNil(t, a1) | ||
dl := e.Registry.getByID("deadletter") | ||
assert.NotNil(t, dl) // should be registered by default | ||
e.Poison(a1).Wait() // poison the a1 actor | ||
e.Send(a1, testMessage{"bar"}) // should end up the deadletter queue | ||
time.Sleep(time.Millisecond) // a flush would be nice here | ||
|
||
// check the log buffer for the deadletter | ||
assert.Contains(t, logBuffer.String(), "deadletter arrived") | ||
|
||
} | ||
|
||
// TestDeadLetterCustom tests the custom deadletter handling. | ||
// It will spawn a new actor, kill it, send a message to it and then check if the deadletter | ||
// received the message. | ||
// It is using the custom deadletter receiver below. | ||
func TestDeadLetterCustom(t *testing.T) { | ||
lh := log.NewHandler(os.Stdout, log.TextFormat, slog.LevelDebug) | ||
e := NewEngine( | ||
EngineOptLogger(log.NewLogger("[engine]", lh)), | ||
EngineOptDeadletter(newCustomDeadLetter)) | ||
a1 := e.Spawn(newTestActor, "a1") | ||
assert.NotNil(t, a1) | ||
dl := e.Registry.getByID("deadletter") | ||
assert.NotNil(t, dl) // should be registered by default | ||
// kill a1 actor. | ||
e.Poison(a1).Wait() // poison the a1 actor | ||
// should be in deadletter | ||
fmt.Println("==== sending message via a1 to deadletter ====") | ||
e.Send(a1, testMessage{"bar"}) | ||
time.Sleep(time.Millisecond) // a flush would be nice here :-) | ||
resp, err := e.Request(dl.PID(), &customDeadLetterFetch{flush: true}, time.Millisecond*10).Result() | ||
assert.Nil(t, err) // no error from the request | ||
assert.NotNil(t, resp) // we should get a response to our request | ||
respDeadLetters, ok := resp.([]*DeadLetterEvent) | ||
assert.True(t, ok) // got a slice of deadletter events | ||
assert.Equal(t, 1, len(respDeadLetters)) // one deadletter event | ||
ev, ok := respDeadLetters[0].Message.(testMessage) | ||
assert.True(t, ok) // should be our test message | ||
assert.Equal(t, "bar", ev.data) | ||
} | ||
|
||
type testActor struct{} | ||
type testMessage struct { | ||
data string | ||
} | ||
|
||
func newTestActor() Receiver { | ||
return testActor{} | ||
} | ||
func (t testActor) Receive(_ *Context) { | ||
// do nothing | ||
} | ||
|
||
type customDeadLetterFetch struct{ flush bool } | ||
|
||
// customDeadLetter is a custom deadletter actor / receiver | ||
type customDeadLetter struct { | ||
deadLetters []*DeadLetterEvent | ||
} | ||
|
||
func newCustomDeadLetter() Receiver { | ||
return &customDeadLetter{ | ||
deadLetters: make([]*DeadLetterEvent, 0), | ||
} | ||
} | ||
|
||
// Receive implements the Receiver interface. This is a OK example of an actor that | ||
// that deals with deadletters. It will store the deadletters in a slice. | ||
func (c *customDeadLetter) Receive(ctx *Context) { | ||
switch ctx.Message().(type) { | ||
case *customDeadLetterFetch: | ||
ctx.Respond(c.deadLetters) | ||
if ctx.Message().(*customDeadLetterFetch).flush { | ||
c.deadLetters = make([]*DeadLetterEvent, 0) | ||
} | ||
case *DeadLetterEvent: | ||
slog.Warn("received deadletter event") | ||
msg, ok := ctx.Message().(*DeadLetterEvent) | ||
if !ok { | ||
slog.Error("failed to cast deadletter event") | ||
return | ||
} | ||
c.deadLetters = append(c.deadLetters, msg) | ||
} | ||
} | ||
|
||
type SafeBuffer struct { | ||
buf bytes.Buffer | ||
mu sync.Mutex | ||
} | ||
|
||
func (sb *SafeBuffer) Write(p []byte) (n int, err error) { | ||
sb.mu.Lock() | ||
defer sb.mu.Unlock() | ||
return sb.buf.Write(p) | ||
} | ||
|
||
func (sb *SafeBuffer) String() string { | ||
sb.mu.Lock() | ||
defer sb.mu.Unlock() | ||
return sb.buf.String() | ||
} | ||
|
||
// Usage in goroutines... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
package actor | ||
|
||
import "time" | ||
import ( | ||
"time" | ||
) | ||
|
||
const ( | ||
defaultInboxSize = 1024 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.