diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8da45d..89f80af 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: ci: strategy: matrix: - go-version: [1.20.x] + go-version: [1.21.x] platform: [ubuntu-latest] runs-on: ${{ matrix.platform }} @@ -29,7 +29,7 @@ jobs: - name: Tester run: go test -v -cover ./... - - name: Linter - uses: golangci/golangci-lint-action@v3 - with: - version: latest \ No newline at end of file + #- name: Linter + # uses: golangci/golangci-lint-action@v3 + # with: + # version: latest \ No newline at end of file diff --git a/README.md b/README.md index 2cf2d53..535440b 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ This is under heavy development, and should not be used before a 1.0.0 release, The Actor Model is a computational model used to build highly concurrent and distributed systems. It was introduced by Carl Hewitt in 1973 as a way to handle complex systems in a more scalable and fault-tolerant manner. -In the Actor Model, the basic building block is an actor, called receiver in Hollywood, which is an independent unit of computation that communicates with other actors by exchanging messages. Each actor has its own state and behavior, and can only communicate with other actors by sending messages. This message-passing paradigm allows for a highly decentralized and fault-tolerant system, as actors can continue to operate independently even if other actors fail or become unavailable. +In the Actor Model, the basic building block is an actor, called receiver in this package, which is an independent unit of computation that communicates with other actors by exchanging messages. Each actor has its own state and behavior, and can only communicate with other actors by sending messages. This message-passing paradigm allows for a highly decentralized and fault-tolerant system, as actors can continue to operate independently even if other actors fail or become unavailable. Actors can be organized into hierarchies, with higher-level actors supervising and coordinating lower-level actors. This allows for the creation of complex systems that can handle failures and errors in a graceful and predictable way. diff --git a/engine.go b/engine.go index a9df14c..e975bf3 100644 --- a/engine.go +++ b/engine.go @@ -2,6 +2,8 @@ package actor import ( "context" + "log/slog" + "reflect" "strings" "sync" ) @@ -43,7 +45,15 @@ func NewEngine(defaultOpts ...Option) *Engine { e.pid.Address = LocalAddress e.deadletter = e.SpawnFunc(func(ctx *Context) { - // TODO: Deadletter stuff + switch ctx.Message().(type) { + case Initialized, Started, Stopped: + // if we have anything, add it here + + default: + // TODO: publish deadletter to Events once we have them + slog.Warn("Deadletter", "to", ctx.sender, "from", ctx.sender, "msg", reflect.TypeOf(ctx.Message())) + } + }, "engine", WithTags("deadletter"), WithInboxSize(defaultInboxSize*4)) e.deadletter.Address = LocalAddress diff --git a/engine_test.go b/engine_test.go index 0f95beb..3420973 100644 --- a/engine_test.go +++ b/engine_test.go @@ -2,7 +2,7 @@ package actor_test import ( "context" - "fmt" + "log/slog" "strconv" "sync" "sync/atomic" @@ -55,15 +55,15 @@ func TestProcessInitStartOrder(t *testing.T) { pid := engine.SpawnFunc(func(c *actor.Context) { switch c.Message().(type) { case actor.Initialized: - fmt.Println("init") + slog.Info("init") wg.Add(1) init = true case actor.Started: - fmt.Println("start") + slog.Info("start") require.True(t, init) started = true case int: - fmt.Println("msg") + slog.Info("msg") require.True(t, started) wg.Done() } @@ -82,7 +82,7 @@ func TestSendMsgRaceCon(t *testing.T) { pid := engine.SpawnFunc(func(c *actor.Context) { msg := c.Message() if msg == nil { - fmt.Println("should never happen") + slog.Error("should never happen") } }, "test") diff --git a/go.mod b/go.mod index a1dae69..627d6de 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/renevo/actor -go 1.20 +go 1.21 require github.com/stretchr/testify v1.8.4 diff --git a/processor.go b/processor.go index a7f50c3..513edbf 100644 --- a/processor.go +++ b/processor.go @@ -2,8 +2,8 @@ package actor import ( "context" - "fmt" - "os" + "log/slog" + "reflect" "sync" "time" ) @@ -61,7 +61,7 @@ func (p *processor) Send(ctx context.Context, _ PID, msg any, from PID) { } if err := p.inbox.Deliver(envelope); err != nil { - fmt.Fprintf(os.Stderr, "failed to deliver message to %s; from: %s; type: %T: %v\n", p.pid, from, msg, err) + slog.Error("Failed to deliver message to inbox.", "inbox", p.pid, "from", from, "msg", reflect.TypeOf(msg), "err", err) } } @@ -158,12 +158,12 @@ func (p *processor) tryRestart(v any) { p.restarts++ if p.restarts >= p.options.MaxRestarts { - fmt.Fprintf(os.Stderr, "Process max restarts exceeded, shutting down: pid: %s; restarts: %d\n", p.pid, p.restarts) + slog.Error("Actor process max restarts exceeded, shutting down.", "pid", p.pid, "restarts", p.restarts) p.cleanup(nil) return } - fmt.Fprintf(os.Stderr, "Process actor restarting: count: %d; maxRestarts: %d; pid: %s; reason: %v\n", p.restarts, p.options.MaxRestarts, p.pid, v) + slog.Warn("Actor process restarting.", "pid", p.pid, "count", p.restarts, "maxRestarts", p.options.MaxRestarts, "err", v) time.Sleep(p.options.RestartDelay) p.Start() } diff --git a/registry.go b/registry.go index d2e4671..7473d75 100644 --- a/registry.go +++ b/registry.go @@ -1,6 +1,10 @@ package actor -import "sync" +import ( + "log/slog" + "reflect" + "sync" +) type registry struct { mu sync.RWMutex @@ -24,8 +28,8 @@ func (r *registry) add(proc Processor) { defer r.mu.Unlock() id := proc.PID().ID - if _, ok := r.lookup[id]; ok { - // TODO: handle duplicates + if existing, ok := r.lookup[id]; ok { + slog.Warn("Attempt to register duplicate process.", "pid", proc.PID(), "existing", reflect.TypeOf(existing), "conflict", reflect.TypeOf(proc)) return } diff --git a/request.go b/request.go index 70f32bd..86232b4 100644 --- a/request.go +++ b/request.go @@ -2,7 +2,9 @@ package actor import ( "context" + "log/slog" "math/rand" + "reflect" "strconv" "sync" "time" @@ -72,7 +74,7 @@ func (c *Context) Request(to PID, msg any, timeout time.Duration) (any, error) { func (c *Context) Respond(msg any) { if c.sender.IsZero() { - // TODO: Log about responding to no one + slog.Warn("Call to Respond with no sender in context.", "pid", c.pid, "msg", reflect.TypeOf(msg)) return }