Skip to content

Fix crash on startup when the database contains no game configuration - #910

Open
sven-n wants to merge 3 commits into
masterfrom
claude/issue-908-vwkrug
Open

sven-n wants to merge 3 commits into
masterfrom
claude/issue-908-vwkrug

Conversation

@sven-n

@sven-n sven-n commented Aug 28, 2026

Copy link
Copy Markdown
Member

Fixes #908.

When the database schema exists but the GameConfiguration is missing (e.g. after a failed import), the Startup project crashed while building the host.

Root causes

  • Program.CreateHostAsync: the ByDataSourceReferenceHandler factory dereferenced the default game configuration id (configId!.Value) without a null check → NullReferenceException. It's resolved while constructing the PlugInManager singleton, so the host never started.
  • Program.CreateMissingPlugInConfigurations: context.GetAsync<GameConfiguration>()...First() threw an InvalidOperationException, because PlugInConfigurationsFactory always tries to create the plugin configurations it finds missing — which is all of them on an empty database.

Changes

Startup

  • Startup/Program.cs: both paths handle a missing game configuration now. The configuration context in the reference handler factory is disposed properly, too.
  • Since the server can now boot into an uninitialized state, the PlugInManager singleton starts without plugin configurations. Program subscribes to SetupService.DatabaseInitialized (before the host, and with it the server containers, is started) and reads the created plugin configurations afterwards.
  • The data source of the plugin manager's reference handler is reloaded at that point. Otherwise the references within the custom plugin configurations would be resolved on a data source which is either not loaded at all (empty database) or holds the previous, deleted game configuration (re-install). The configurations are taken from that reloaded data source, so they stay alive as long as the reference handler does, and the registered ICollection<PlugInConfiguration> is updated as well.

Plugin manager

  • The configuration loop of the constructor moved into a public ReadConfigurations(IEnumerable<PlugInConfiguration>), which can be called again when the configurations become available later.
  • Reading the configurations again can also activate a plugin now, which is required when it was inactive in the previously read data. Previously read configurations aren't observed anymore, so the PropertyChanged handlers don't pile up.
  • A failing configuration doesn't stop the remaining ones from being read anymore.

Setup service and page

  • SetupService.CreateDatabaseAsync awaits the subscribers of its DatabaseInitialized event one after another. A multicast delegate only returns the task of the last subscriber, so the plugin configurations were applied while the server containers were already restarting.
  • The new DataInitializationState (Initialized, NotInitialized, Unknown) tells a failed or timed out check apart from an actually empty database. The setup page only offers the unconfirmed Create button for NotInitialized; Unknown keeps the confirmed ReInstall button.
  • The page determines the state again after a schema update (it can't be determined on an outdated schema) and loads it before it hides the installer.

Testing

  • Full solution builds without errors.
  • MUnique.OpenMU.Tests (984), MUnique.OpenMU.Web.Tests (95), MUnique.OpenMU.PlugIns.Tests (41), MUnique.OpenMU.ChatServer.Tests (26) and MUnique.OpenMU.Persistence.Initialization.Tests (21, 6 skipped) pass.
  • Not verified by an end-to-end startup against a real PostgreSQL instance - that wasn't available in the used environment.

Generated by Claude Code

The server crashed when the database schema existed but the game
configuration was missing, e.g. after a failed data import:

* The reference handler factory dereferenced the default game
  configuration id without checking for null.
* The plugin configuration factory tried to create the missing plugin
  configurations and called First() on an empty game configuration set.

Both cases are now handled gracefully, so the server starts up and the
admin panel offers to install the data. Because the plugin
configurations only exist after the data initialization, they are now
read into the PlugInManager when the database gets initialized, before
the server containers are restarted.

The setup page now shows the same "Create" action as for a
non-existing database when the data isn't initialized, and refreshes
its state after an installation.

Closes #908

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SF8VVTJtUjimJfVKa32Lxm

@sven-n sven-n left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The startup fix works: an empty database no longer crashes the server. The follow-up after an install has problems, though:

  • Plugin configurations fail after an install. PlugInManager keeps the reference handler it got at startup, whose data source never loaded a game configuration. The first custom configuration with a reference throws and stops the loop (see Program.cs:423).
  • The new "Create" button wipes the database without asking. IsDataInitializedAsync returns false on a timeout or error, so a populated database can land on this branch (see Setup.razor:36).
  • This handler doesn't finish before the servers restart. The multicast AsyncEventHandler only returns the last subscriber's task, so the handler races RestartAllAsync (see Program.cs:344).

The rest are smaller follow-ups and nits.


Generated by Claude Code

Comment thread src/Startup/Program.cs
Comment thread src/Web/AdminPanel/Pages/Setup.razor Outdated
Comment thread src/Startup/Program.cs
Comment thread src/Web/AdminPanel/Pages/Setup.razor Outdated
Comment thread src/PlugIns/PlugInManager.cs Outdated
Comment thread src/Startup/Program.cs Outdated
Comment thread src/Web/AdminPanel/Pages/Setup.razor.cs Outdated
Comment thread src/Startup/Program.cs Outdated
Comment thread src/Startup/Program.cs Outdated
* The data source of the plugin manager's reference handler is reloaded
  when the database got initialized. Otherwise, the references of the
  custom plugin configurations were resolved on a data source which was
  either not loaded at all (empty database) or holds the previous,
  deleted game configuration (re-install).
* The plugin configurations are taken from that reloaded data source, so
  they stay alive as long as the reference handler does. The registered
  collection of plugin configurations is updated as well.
* A failing plugin configuration doesn't stop the remaining ones from
  being read anymore.
* Reading the configurations again can now also activate a plugin, which
  is required when it was inactive in the previously read data.
* Previously read configurations are not observed anymore when they are
  read again, so the property changed handlers don't pile up.
* SetupService.CreateDatabaseAsync awaits the subscribers of its
  DatabaseInitialized event one after another. A multicast delegate only
  returns the task of the last subscriber, so the plugin configurations
  were applied while the server containers were already restarting.
* The setup page only offers to create the data without a confirmation
  when it knows for sure that the database contains no game
  configuration. A failed or timed out check is reported as unknown
  state now, which keeps the confirmed re-install button. The state is
  also determined again after a schema update, because it can't be
  determined on an outdated schema.
* The setup page loads the state before it hides the installer, so it
  doesn't show the state of the uninitialized database for a moment.
* Removed the redundant game configuration check in the plugin
  configuration factory.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SF8VVTJtUjimJfVKa32Lxm
Resolved the conflicts in the setup page: master moved the SetupService
to the persistence project and added the backup/snapshot sections, while
this branch replaced the _isDataInitialized flag by the tri-state
DataInitializationState. The enum moved to the persistence project as
well, so that it stays next to the SetupService.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SF8VVTJtUjimJfVKa32Lxm
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 22, 2026

Copy link
Copy Markdown

Deploying openmudocs with  Cloudflare Pages  Cloudflare Pages

Latest commit: c807ca4
Status: ✅  Deploy successful!
Preview URL: https://7e3ea4b6.openmudocs.pages.dev
Branch Preview URL: https://claude-issue-908-vwkrug.openmudocs.pages.dev

View logs

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Server crashes on startup with empty database

2 participants