Skip to content

fix: read inactive members on the main thread for async income previews - #244

Merged
XxFran10xX merged 1 commit into
mainfrom
fix/inactive-members-async-preview
Nov 12, 2026
Merged

XxFran10xX merged 1 commit into
mainfrom
fix/inactive-members-async-preview

Conversation

@XxFran10xX

Copy link
Copy Markdown
Contributor

Problem

Since 4.28.0 (#238), every law, trade agreement and favour/repress income estimate on Main has shown "Income estimate unavailable" (a player reported it on Discord). Main's log had 96 Economic impact preview failed warnings on 11-11 and 11-12:

IllegalStateException: Character identity must be used on the main thread
  tlibs Characters.requireMainThread
  Members.isOnline <- InactivityService.isOnline <- isMemberInactive <- activeOnly
  <- Guild.getMemberPercentage <- getStabilityEffect <- getRepressFavourCost
  <- Government.getTotalUpkeep <- Faction.getPenalty <- Ledger.getIncome
  <- EconomicPreview.nets <- project <- trade / law / favour

The previews run off the server thread inside FrozenReads.read. Member shares skip members 28+ days offline, and the "is this member online?" check asks TLibs, which refuses other threads. A preview totals every guild, so one inactive member anywhere on the server (Main has 162 of 445 tracked members past 28 days) broke every estimate for everyone.

Fix

FrozenReads.capture() (server thread) also captures the inactive members, the same way #167 and #178 froze balances and Spymaster deaths:

  • InactivityService.captureInactiveMembers(): one pass over the offline day counts (FactionCleanup.membersOffline), minus the character keys being played now. That is one session lookup per online player instead of one TLibs lookup per member past the threshold, and none at all when nobody is past it.
  • Inside a frozen read, isMemberInactive is a set lookup. The worker no longer calls TLibs, and no longer reads FactionCleanup.offlineDays, a plain HashMap the server thread writes on joins and on the daily tick.
  • Outside a frozen read the live check is unchanged (menus, councils, elections, war code).

This covers every async preview path that uses FrozenReads: law, favour, trade, tariff and tax impacts, branch estimates and the background trade recalculation.

Tests

  • New FrozenInactiveMembersTest:
    • live and frozen answers agree for each kind of member (away, online past the threshold, under the threshold, untracked, unknown);
    • once lookups are refused, the live check throws Main's exception while the frozen read answers, on a worker thread too;
    • day counts written after the capture don't change it;
    • no character lookup when nobody is past the threshold;
    • players without a character count as nobody online;
    • a null day count reads as 0;
    • an unreadable day-count file marks nobody inactive.
  • Removing the frozen branch makes the new test fail with the exact live exception.
  • Locally, the inactivity, FrozenReads, EconomicPreview, EconomicImpact* and BranchIncome* tests pass (137). The rest of the local Windows run has the same directory-fsync failures as unmodified main, so Linux CI decides.

Dev check

Bot ~/sf-inactive-preview/test.js (TestChar1 opens the trade menu toward Dsvguild; Dev has 1,463 members past 28 days):

  • 4.32.1 (before): all 3 trade estimates read "Income estimate unavailable", and the log has 3 Economic impact preview failed / main-thread errors.
  • This branch: all 3 show "Estimated Economic Impacts", and the log has no preview failure or main-thread error.

🤖 Generated with Claude Code

Since 4.28.0 (#238) every law, trade and favour income estimate on Main
showed "Income estimate unavailable". Member shares in stability and
upkeep skip members 28+ days offline, and whether such a member is
online asks TLibs, which refuses lookups off the main thread, so the
preview worker threw on the first guild with an inactive member.

FrozenReads now also captures the inactive members with each preview
copy: one pass over the offline day counts, minus the characters being
played now (one session lookup per online player, none when nobody is
past the threshold). Inside a frozen read isMemberInactive is a set
lookup, so the worker no longer calls TLibs or reads the day-count map
the server thread writes. The live check outside previews is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Explain Change →

No actionable comments were generated in the recent review.

Summary

Summary by CodeRabbit

  • Improvements
    • Inactivity checks now use a consistent snapshot of members who are offline beyond the configured threshold, helping keep results stable during an operation.
    • Online members and members without sufficient inactivity records are excluded from the inactive-member results.
    • If inactivity data cannot be read, the results safely contain no offline members.

The change captures inactive-member names in FrozenReads snapshots. InactivityService uses the captured set during frozen reads and retains its live inactivity check when no snapshot is active. New tests cover capture results and edge cases.

Changes: Inactive-member snapshot

Layer / File(s) Summary
Collect inactive members
src/main/java/net/tfminecraft/simplefactions/utils/FactionCleanup.java, src/main/java/net/tfminecraft/simplefactions/inactivity/InactivityService.java
FactionCleanup.membersOffline filters stored offline-day counts by a predicate. InactivityService.captureInactiveMembers excludes online character keys from the matching members.
Capture and use frozen inactive members
src/main/java/net/tfminecraft/simplefactions/economy/FrozenReads.java, src/main/java/net/tfminecraft/simplefactions/inactivity/InactivityService.java, src/test/java/net/tfminecraft/simplefactions/inactivity/FrozenInactiveMembersTest.java
FrozenReads stores and exposes the captured inactive-member set. InactivityService.isMemberInactive checks the frozen set when available. Tests cover snapshot results, lookup behaviour and edge cases.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant FrozenReads
  participant InactivityService
  participant FactionCleanup
  FrozenReads->>InactivityService: Capture inactive members
  InactivityService->>FactionCleanup: Get members matching offline-day predicate
  FactionCleanup-->>InactivityService: Return offline members
  InactivityService-->>FrozenReads: Return inactive-member set
  FrozenReads-->>InactivityService: Provide frozen inactive-member set during read
Loading

Suggested reviewers: ryanbarlow97, drefvelin

Merge Risk: Minimal · up to 75c93

The change makes income previews read inactive members from a snapshot captured on the server thread, which should fix previews reported as unavailable. No merge-blocking risk was found, and new tests cover the capture and fallback cases.

- [ ] Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks 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

Recent review info

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: a6678a40-8e3b-4fad-aa11-22b8f2518f76
Commits

Reviewing files that changed from the base of the PR and between 0c034cc and 75c93f2.

Files selected for processing: 4

  • src/main/java/net/tfminecraft/simplefactions/economy/FrozenReads.java
  • src/main/java/net/tfminecraft/simplefactions/inactivity/InactivityService.java
  • src/main/java/net/tfminecraft/simplefactions/utils/FactionCleanup.java
  • src/test/java/net/tfminecraft/simplefactions/inactivity/FrozenInactiveMembersTest.java

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

@XxFran10xX

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@github-actions github-actions Bot added the size:M 30-99 effective changed lines (tests excluded in mixed PRs). label Nov 12, 2026
@coderabbitai

coderabbitai Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@XxFran10xX
XxFran10xX merged commit 830c4b3 into main Nov 12, 2026
3 checks passed
@XxFran10xX
XxFran10xX deleted the fix/inactive-members-async-preview branch November 12, 2026 11:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 effective changed lines (tests excluded in mixed PRs).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant