Great Cleanup & Improvements #70
No reviewers
Labels
No labels
blocked
bug
dependencies
documentation
duplicate
enhancement
github_actions
go
good first issue
help wanted
in-progress
invalid
question
ready-to-fix
source:discord
source:sentry
source:server-logs
triage-needed
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
pokebedrock/pokebedrock-hub!70
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "great-improvements-and-cleanup"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
All audit items are now fixed and the project builds cleanly. Summary of what changed:
Critical (race conditions / panics / leaks)
pokebedrock/queue/manager.go— replaced theatomic.Value[PriorityQueue]+ RMW pattern with async.Mutex-guardedPriorityQueue. All queue mutations now go through the mutex;Updatecollects entries to remove during a snapshot iteration and removes them by identity (usingentry.indexwith a stale-index fallback) rather than by stale indices, so concurrentAddPlayer/RemovePlayercalls can no longer drop the wrong heap node or corrupt invariants.pokebedrock/session/ranks.go— collapsed therankLoadQueue+rankUpdateChdouble-queue into a singlerankUpdateChconsumed by 3 worker goroutines.Ranks.Loadis now fire-and-forget, so the close-channel panic race is structurally impossible.StopRankChannelwaits on aWaitGroupfor clean shutdown.pokebedrock/moderation/service.go— eliminateddefer resp.Body.Close()inside retry loops by extractingattempt,decodeInflictionsResponse,decodeNoContentResponse, andcloseBodyhelpers; bodies are now closed per attempt.closedisatomic.Bool. Alls.log.Debug(fmt.Sprintf(...))were replaced with structuredslogfields, removing the per-requestbytes.Bufferallocation.pokebedrock/rank/service.go— samedefer-in-loop fix viafetchRoleshelper.closedisatomic.Bool.pokebedrock/vpn/service.go— same fix viahandleResponse.pokebedrock/restart/service.go—closedisatomic.Bool.cleanupExpiredEntriesnowselects on adonechannel soStop()shuts the goroutine down promptly instead of waiting up to a full minute.Significant (performance / correctness)
pokebedrock/pokebedrock.go— replaced theswitchstatement instartTickingwith independentifblocks sosrv.UpdateAll,slapper.UpdateAll, anddoAFKCheckactually run on every cadence they're supposed to.pokebedrock/vpn/cache.go— rewritten with a debounced flusher goroutine:Setmarks the cache dirty and signals; the flusher writes at most once perflushInterval(5 s), using atmp+renamefor atomic disk replacement.Stop()flushes pending writes deterministically. ServiceStop()now callscache.Stop().pokebedrock/session/inflictions.go—inflictionWorkerandinflictionLoadWorkernow track in-flight requests with async.WaitGroupinstead of a fragile fixed-capacity channel;StopInflictionWorkerblocks on a real wait group with a 3-second cap rather than always sleeping the full timer.pokebedrock/form/moderate.go—formatExpiryhelper formats*int64expiry timestamps correctly; the previous%dprinted the pointer address.pokebedrock/command/kick.go—o.Printnow logs the actual victim name instead of the entire target list per iteration.Build, vet, and tests pass clean. The only remaining lint warnings are pre-existing cognitive-complexity / exhaustive-switch flags on
CreateInfliction.SubmitandRemoveInfliction.Submit, which were untouched by this change set.Summary by CodeRabbit
New Features
Bug Fixes
Performance
UX
ℹ️ Recent review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID:
9f2bde8e-721b-4400-b64c-7a743347c598📥 Commits
Reviewing files that changed from the base of the PR and between
b4bc3199c5andfc24b35f33.⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum📒 Files selected for processing (1)
go.mod📝 Walkthrough
Walkthrough
Moves blocking I/O out of world transaction callbacks, centralizes HTTP handling, introduces atomic shutdown flags and deterministic worker shutdown, debounces VPN cache writes, and refactors queue and session worker pools. Parkour countdown callbacks receive player context inside ExecWorld to avoid entity lookups. Go toolchain and dependencies updated.
Changes
Concurrency Refactoring and Service I/O Isolation
.cursor/rules/no-blocking-io-in-execworld.mdcExecWorldandcmd.Command.Runcallbacks execute on the world transaction goroutine and must not perform blocking I/O; shows fetch-offthread-apply examples and referencesrankWorkerandprocessInflictionRequest.pokebedrock/moderation/service.go,pokebedrock/rank/service.go,pokebedrock/restart/service.gobool closedtoatomic.Booland add done/shutdown channels where needed to enable prompt worker/goroutine termination.pokebedrock/moderation/service.gos.attempt(), addsctxCloser/closeBodyhelpers, and consolidates response decoding plus player-details worker/shutdown/send logic.pokebedrock/form/moderate.go,pokebedrock/command/kick.goformatExpiry()added; kick spawns async per-target sync goroutines and logs failures with slog.pokebedrock/vpn/cache.go,pokebedrock/vpn/service.goStop()to flush on shutdown and re-mark dirty on write error; CheckIP delegates per-attempt response handling to helper that ensures body closure.pokebedrock/queue/manager.gosnapshot(), locked removal helpers,positionFor(), and rewritesUpdate/NextPlayer/GetQueuePosition.pokebedrock/session/inflictions.gosync.WaitGroupandsync.Oncefor deterministicStopInflictionWorker(); replace counting channel with per-request waitgroups and semaphore; centralize fetch+apply inprocessInflictionRequestusing ExecWorld read then re-enter ExecWorld to apply.pokebedrock/session/ranks.gorankUpdateCh; addrankShutdownandrankWorkerWG; consolidateprocessRankUpdateand update Ranks enqueue/accessor APIs (Ranks(),HasRank,HasRankOrHigher,LastRankFetch).pokebedrock/rank/service.gofetchRolessingle-attempt helper, drain/close response bodies per attempt, reorganize status handling, and use retry loop honoring atomic closed flag.pokebedrock/restart/service.gosync.Onceto close done channel and set atomic closed flag; cleanup loop exits by selecting on done closure.pokebedrock/pokebedrock.gopokebedrock/parkour/session.go,pokebedrock/parkour/manager.go*world.EntityHandle;beginCountdowncallbacks accept*player.Player(and*world.Txon done) and are invoked insidehandle.ExecWorld; StartCourse/restartFromCheckpoint updated accordingly.go.modgo1.26.3, update direct dependencies for dragonfly, gophertunnel, and golang.org/x/text, and remove the dragonfly replace directive.Sequence Diagram(s)
The following diagram visualizes the core pattern introduced across multiple services: moving blocking I/O outside of world transaction callbacks and coordinating clean shutdown via atomic flags and channels.
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~65 minutes
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings.
✨ Finishing Touches
🧪 Generate unit tests (beta)
great-improvements-and-cleanupThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
❤️ Share
Comment
@coderabbitai helpto get the list of available commands and usage tips.@bugbot review
Pull request overview
This PR focuses on eliminating shutdown races/resource leaks and improving correctness/performance in several background services (queueing, ranks/inflictions workers, moderation/rank/VPN HTTP clients), while also tightening a few gameplay/admin UX details.
Changes:
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.
Show a summary per file
closedatomic.*int64and moves moderation calls off transaction goroutine.💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The shutdown path drains
activeRequestsin the worker (for range len(activeRequests) { <-activeRequests }), but each in-flight goroutine also does<-activeRequestsin its deferred cleanup. This can deadlock/leak goroutines: the worker may consume the tokens first (or block waiting for tokens that goroutines already consumed), and then the goroutines block forever on<-activeRequests. Use async.WaitGroup(as done insession/inflictions.go) or another mechanism where only one side is responsible for decrementing, and haveStop()wait on that with a timeout if desired.For 5xx responses,
fetchRolesreturns an error that does not wrapErrServer(fmt.Errorf("unexpected status code: %d", ...)).RolesErrorrelies onerrors.Is(err, ErrServer)to map server-side failures to a user-friendly message, so 5xx responses will now fall through to the generic default case. Consider wrappingErrServerfor >=500 responses as well (while still marking it retryable) so error classification remains consistent.stopOnceis declared at package scope, but it guards closings.done, which is per-Service instance. IfNewServiceis called more than once in-process (tests, reloads, multiple hubs), only the first instance will ever close itsdonechannel; subsequent instances' cleanup goroutines will never be signalled to stop. Make thesync.Oncea field onService(or reuses.closed/s.donewith a non-global guard) so shutdown is correct per instance.@ -72,0 +123,4 @@if err != nil {update.ranks.SetRanks([]rank.Rank{rank.UnLinked})msg := text.Colourf("<red>%s</red>", rank.RolesError(err))StopRankChannelclosesrankShutdownand then waits for the workers to exit, but closingrankShutdownmakes workers return immediately without drainingrankUpdateCh(the select will prefer the shutdown case once it's closed). If the intent is to drain pending rank updates before exit, consider instead closingrankUpdateCh(to stop new work) and having workers range until the channel is drained, or update the comment/behaviour so callers don't assume queued updates will be processed during shutdown.handleResponsecloses the response body without draining it. In the 429/default cases you don't read from the body at all, which prevents HTTP connection reuse in Go's transport and can increase connection churn under load. Consider draining toio.Discardbefore closing (similar torank/service.go'sfetchRolesandmoderation/service.go'scloseBody) to keep keep-alives effective.Actionable comments posted: 2
🧹 Nitpick comments (5)
🤖 Prompt for all review comments with AI agents
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID:
fb155b71-08b1-422a-a62b-c80b2b9509e2📥 Commits
Reviewing files that changed from the base of the PR and between
4c3e26bd56and323ed9c319.📒 Files selected for processing (12)
.cursor/rules/no-blocking-io-in-execworld.mdcpokebedrock/command/kick.gopokebedrock/form/moderate.gopokebedrock/moderation/service.gopokebedrock/pokebedrock.gopokebedrock/queue/manager.gopokebedrock/rank/service.gopokebedrock/restart/service.gopokebedrock/session/inflictions.gopokebedrock/session/ranks.gopokebedrock/vpn/cache.gopokebedrock/vpn/service.go⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
5xx errors lose the
ErrServersentinel, soRolesErrormis-classifies them after retries are exhausted.Line 145 returns
retry=truewith a plainfmt.Errorf("unexpected status code: %d", ...)that doesn't wrapErrServer. When all retries are exhausted, that error becomeslastErrand bubbles up toRolesError, which only matchesErrServerfor the localizederror.server_error_fetching_rolesmessage. 5xx exhaustion will instead hit the default branch ("Failed to fetch roles %s"), regressing the user-facing error message that the non-5xx default branch correctly preserves.🔧 Proposed fix to wrap 5xx errors with ErrServer
📝 Committable suggestion
🤖 Prompt for AI Agents
@ -252,4 +279,3 @@}// Use the original Load method directlyinf.Load(handle)⚠️ Potential issue | 🟠 Major | ⚡ Quick win
Reset infliction state from the full response.
This path only ever sets
muted,frozen, andmuteDurationwhen an infliction exists. If the moderation API later returns no mute/freeze, the old local state survives, so lifted inflictions can remain applied locally.Suggested fix
📝 Committable suggestion
🤖 Prompt for AI Agents
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID:
ab367934-0be7-4eb2-8f5b-61179b1b0e81📥 Commits
Reviewing files that changed from the base of the PR and between
323ed9c319andb4bc3199c5.⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum📒 Files selected for processing (3)
go.modpokebedrock/parkour/manager.gopokebedrock/parkour/session.go⚠️ Potential issue | 🟠 Major | ⚡ Quick win
Guard callbacks against late cancellation windows.
Line 117 checks
stoponly before schedulingonDone; ifstopCountdown()closes right after that check,onDonecan still run. The same window exists fortickscheduling. Re-checkstopinside eachExecWorldcallback before invoking user callbacks.Suggested fix
🤖 Prompt for AI Agents