Skip to content

A throwing service Dispose abandons the rest of the provider #455

Description

@botoddly

Problem

ServiceProvider.Dispose sets _disposed = true before it disposes anything, then walks its children, its transient disposal records, and its creation records in reverse order. None of the disposal calls are guarded, so the first service whose Dispose throws aborts the whole method.

What is left behind:

  • Every service earlier in the iteration order is never disposed. For services holding GPU or SDL handles, those are leaked for the lifetime of the process.
  • Remaining child providers are unreachable. _children is set to null before the child loop runs, so children that were not reached cannot be disposed by any later call.
  • The field cleanup at the end of the method never runs, so _services, _creationRecords, and the caches stay populated and keep every instance alive.
  • _disposed is already true, so a retry returns immediately. The provider reports itself disposed while most of it is not.

A single misbehaving service therefore silently disables disposal for everything else in the provider, and there is no way to detect it or recover.

This is reachable from ordinary use. StageManager.ReplaceStage disposes the outgoing stage provider on every stage transition, so one throwing service leaks the entire stage.

Expected behavior

  • One failing Dispose does not prevent the remaining services and child providers from being disposed.
  • The provider finishes its own cleanup regardless.
  • Disposal failures are surfaced rather than swallowed, so a throwing Dispose is still visible as a defect.

Possible approach

Continue the loops on failure and collect the exceptions, then rethrow them together once the provider has finished:

  • wrap each RunDisposingCallbacks and Dispose call so a throw records the exception and moves on;
  • run the child loop, both service loops, and the field cleanup to completion;
  • if anything was collected, throw a single AggregateException at the end.

Aggregating matches the observation that disposal has no meaningful partial-failure recovery: the caller cannot fix it, but it must not be hidden.

Coverage

  • a throwing service Dispose still disposes the services registered before it;
  • a throwing child provider Dispose still disposes the parent's own services and the remaining children;
  • the provider's caches and records are cleared even when a disposal throws;
  • the exception from a throwing Dispose reaches the caller;
  • multiple throwing services produce one exception carrying all of them;
  • disposal with no failures behaves exactly as it does now, including reverse creation order and alias deduplication.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions