Repository navigation
Add embedded package for in-process queues - #9
Merged
Merged
Conversation
Moving the server under internal/ (#7) left other Go modules with no way to run SMQ inside their own process. Add a public embedded package that opens the SQLite store directly, with no listener. It provides an idempotent CreateTopic, Publish and an atomic PublishBatch that return offsets, Read from an offset bounded by count and bytes, Earliest and Latest offsets, DeleteUntilOffset, per-topic Stats, and Close. An optional RegisterGRPC serves the existing SMQ API from the same store on a caller-owned grpc.Server. The package reuses the internal SQLite store, which gains: - AddMessages, ReadMessages, TopicStats and Topics. - Open with options. WAL, synchronous, auto_vacuum and busy_timeout are now set through the DSN, so they apply to every pooled connection. Previously they applied to one. Private in-memory databases are pinned to a single connection. - A fix for concurrent producers receiving each other's offsets. The per-topic writer goroutine shared one result channel; on the base branch, 258 of 3,200 concurrent AddMessage calls got the wrong offset. Inserts now run directly under the existing write lock. - ErrStoreClosed for operations after Close, instead of a send on a closed channel. The gRPC transport maps it to UNAVAILABLE. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Suhaibinator
added a commit
that referenced
this pull request
Oct 11, 2026
Moving the server under internal/ (#7) left other Go modules with no way to run SMQ inside their own process. Add a public embedded package that opens the SQLite store directly, with no listener. It provides an idempotent CreateTopic, Publish and an atomic PublishBatch that return offsets, Read from an offset bounded by count and bytes, Earliest and Latest offsets, DeleteUntilOffset, per-topic Stats, and Close. An optional RegisterGRPC serves the existing SMQ API from the same store on a caller-owned grpc.Server. The package reuses the internal SQLite store, which gains: - AddMessages, ReadMessages, TopicStats and Topics. - Open with options. WAL, synchronous, auto_vacuum and busy_timeout are now set through the DSN, so they apply to every pooled connection. Previously they applied to one. Private in-memory databases are pinned to a single connection. - A fix for concurrent producers receiving each other's offsets. The per-topic writer goroutine shared one result channel; on the base branch, 258 of 3,200 concurrent AddMessage calls got the wrong offset. Inserts now run directly under the existing write lock. - ErrStoreClosed for operations after Close, instead of a send on a closed channel. The gRPC transport maps it to UNAVAILABLE. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Suhaibinator
added a commit
that referenced
this pull request
Oct 11, 2026
* refactor: organize project and modernize CI * ci: compare protobuf against remote main * docs: add client migration guide * ci: authenticate Buf setup downloads * feat: add embedded package for in-process queues (#9) Moving the server under internal/ (#7) left other Go modules with no way to run SMQ inside their own process. Add a public embedded package that opens the SQLite store directly, with no listener. It provides an idempotent CreateTopic, Publish and an atomic PublishBatch that return offsets, Read from an offset bounded by count and bytes, Earliest and Latest offsets, DeleteUntilOffset, per-topic Stats, and Close. An optional RegisterGRPC serves the existing SMQ API from the same store on a caller-owned grpc.Server. The package reuses the internal SQLite store, which gains: - AddMessages, ReadMessages, TopicStats and Topics. - Open with options. WAL, synchronous, auto_vacuum and busy_timeout are now set through the DSN, so they apply to every pooled connection. Previously they applied to one. Private in-memory databases are pinned to a single connection. - A fix for concurrent producers receiving each other's offsets. The per-topic writer goroutine shared one result channel; on the base branch, 258 of 3,200 concurrent AddMessage calls got the wrong offset. Inserts now run directly under the existing write lock. - ErrStoreClosed for operations after Close, instead of a send on a closed channel. The gRPC transport maps it to UNAVAILABLE. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Since #7 moved the server under
internal/, other Go modules can't run SMQ in their own process. They used to callserver.NewServer(port, dbPath),go s.Start()ands.Driver.CreateTopic(...). One consumer needs a durable local outbox: publish captured events, read them in batches from an offset, forward them upstream over its own gRPC link, delete through the acknowledged offset, and report topic depth in heartbeats. It doesn't need SMQ's listener.Base branch
This PR is stacked on #8 (
codex/project-structure-ci), which is open and not merged. #8 restructures the store (internal/server/database→internal/store/sqlite, plusinternal/queue), and this change builds on that layout. Basing it onmainwould conflict with #8 everywhere. Once #8 merges, retarget this PR tomain. Note:ci.ymlruns only for pull requests intomain, so CI will run after the retarget. Until then, the checks below were run locally.What's added
A new public package,
github.com/Suhaibinator/SuhaibMessageQueue/embedded:Semantics:
AUTOINCREMENT).Readis inclusive offromOffset.MaxBytescaps a batch's total payload size, but the first message is always returned, so one oversized message can't stall a reader. The byte cutoff is computed fromLENGTH(data)before any payloads are loaded.RegisterGRPCserves the existing wire API from the same store on agrpc.Serverthe caller owns. This covers the old embedded-server use case. Remote writes are opt-in.embeddedreuses the internal SQLite store; it adds no new SQL schema. There are no protobuf or database-format changes.Store changes in
internal/store/sqliteAddMessagecalls got the wrong offset. This also affected gRPCProduce. Inserts now run directly under the existingdbMuxwrite lock, which already serialized them. A regression test is added.Closeracing a publish could panic (send on a closed channel). Operations afterClosenow returnqueue.ErrStoreClosed, which gRPC maps toUNAVAILABLE.CloseandDeleteTopicnow take locks in the same order asCreateTopic.synchronous,auto_vacuumandbusy_timeoutare set through the DSN, so they apply to every connection in thedatabase/sqlpool. Before, they ran once withdb.Execand applied to only one connection. The mattn build already defaults WAL connections tosynchronous=NORMAL, so the server's durability doesn't change.Open(path, Options)lets embedders chooseFULL.:memory:databases are pinned to one connection, since every pooled connection otherwise opens a different, empty database.AddMessages(transactional batch),ReadMessages(count and byte bounds),TopicStats,Topics.Docs
serverpackage. It also addsUNAVAILABLEto the status-code table.embedded/example_test.go: a runnable outbox example that is checked bygo test.fmtnow includesembedded/.Testing
Run locally with Go 1.27.2 (
go.modtoolchain is go1.26.5), mirroring the Go job inci.yml:gofmt -l $(git ls-files '*.go'): cleango vet ./...: cleango test -race -shuffle=on -coverprofile=coverage.out ./...: all packages pass.embeddedstatement coverage is 93.8%.go test -race -count=15 -shuffle=on ./embedded/ ./internal/store/sqlite/: passgo build -trimpath ./cmd/smq: ok.go mod tidyleaves no diff.embeddedtests cover publish/read-back, batch and byte limits, delete-until (including offsets not being reused), reopening the database, concurrent publishers whose returned offsets are checked against stored payloads, stats, close-while-publishing, in-memory databases, and gRPC registration in both read-only and write modes. Protobuf and Python are unchanged, sobufand the Python checks were not run.🤖 Generated with Claude Code