dispose method
Rejects new operations and runs cleanup once accepted leases are released.
The first call supplies the cleanup closure and its arguments. Later calls return the identical future and ignore their closure. Cleanup runs in the first call's zone. Future errors keep their error and stack; synchronous exceptions follow that zone's error interception. Success and failure close the resource permanently. Releasing a lease does not throw cleanup errors. Attached streams cancel before cleanup. A cleanup failure takes priority; otherwise the first cancellation failure in attachment order is forwarded.
Cleanup may reenter dispose, but must not await its own disposal. Returning that future directly is rejected; an indirect circular wait cannot be detected. As with other Dart futures, errors follow the creation error zone.
Implementation
Future<void> dispose(FutureOr<void> Function() cleanup) {
final existing = _completion;
if (existing != null) return existing.future;
final completion = Completer<void>();
final zone = Zone.current;
_completion = completion;
_cleanup = () => zone.run(() {
try {
final result = cleanup();
if (identical(result, completion.future)) {
throw StateError('Resource cleanup cannot await its own disposal.');
}
if (result is Future<void>) {
// Forward an existing asynchronous error without invoking the
// disposal zone's errorCallback a second time.
final cancellation = _firstCancellationError;
final settled = cancellation == null
? result
: result.then<void>((_) => cancellation);
completion.complete(settled.whenComplete(() => _owner = null));
} else {
_finish();
}
} catch (error, stack) {
_finish(error, stack);
}
});
final streams = _streams;
if (streams == null) {
if (_operations == 0) _runCleanup();
} else {
_cancelStreams(streams.toList(growable: false));
_tryCleanup();
}
return completion.future;
}