feat(core): add full DI lifecycle observability via onInstanceDisposed

- Call observer.onInstanceDisposed for every removed/cleaned-up instance in Scope lifecycle
- Makes instance disposal fully observable outside of cache events
- Ensures analytics/logging frameworks get notified of each object removal from memory

Part of complete CherryPickObserver integration for transparent diagnostics and monitoring of DI container.
This commit is contained in:
Sergey Penkovsky
2025-08-11 18:17:32 +03:00
parent efed72cc39
commit 2ec3a86a2f

View File

@@ -43,6 +43,7 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
Scope(this._parentScope, {required CherryPickObserver observer}) : _observer = observer { Scope(this._parentScope, {required CherryPickObserver observer}) : _observer = observer {
setScopeId(_generateScopeId()); setScopeId(_generateScopeId());
observer.onScopeOpened(scopeId ?? 'NO_ID');
observer.onDiagnostic( observer.onDiagnostic(
'Scope created: ${scopeId ?? 'NO_ID'}', 'Scope created: ${scopeId ?? 'NO_ID'}',
details: { details: {
@@ -113,6 +114,7 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
if (childScope.scopeId != null) { if (childScope.scopeId != null) {
GlobalCycleDetector.instance.removeScopeDetector(childScope.scopeId!); GlobalCycleDetector.instance.removeScopeDetector(childScope.scopeId!);
} }
observer.onScopeClosed(childScope.scopeId ?? name);
observer.onDiagnostic( observer.onDiagnostic(
'SubScope closed: $name', 'SubScope closed: $name',
details: { details: {
@@ -134,6 +136,12 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
/// return [Scope] /// return [Scope]
Scope installModules(List<Module> modules) { Scope installModules(List<Module> modules) {
_modulesList.addAll(modules); _modulesList.addAll(modules);
if (modules.isNotEmpty) {
observer.onModulesInstalled(
modules.map((m) => m.runtimeType.toString()).toList(),
scopeName: scopeId,
);
}
for (var module in modules) { for (var module in modules) {
observer.onDiagnostic( observer.onDiagnostic(
'Module installed: ${module.runtimeType}', 'Module installed: ${module.runtimeType}',
@@ -161,6 +169,12 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
/// ///
/// return [Scope] /// return [Scope]
Scope dropModules() { Scope dropModules() {
if (_modulesList.isNotEmpty) {
observer.onModulesRemoved(
_modulesList.map((m) => m.runtimeType.toString()).toList(),
scopeName: scopeId,
);
}
observer.onDiagnostic( observer.onDiagnostic(
'Modules dropped for scope: $scopeId', 'Modules dropped for scope: $scopeId',
details: { details: {
@@ -186,6 +200,7 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
/// return - returns an object of type [T] or [StateError] /// return - returns an object of type [T] or [StateError]
/// ///
T resolve<T>({String? named, dynamic params}) { T resolve<T>({String? named, dynamic params}) {
observer.onInstanceRequested(T.toString(), T, scopeName: scopeId);
// Используем глобальное отслеживание, если включено // Используем глобальное отслеживание, если включено
T result; T result;
if (isGlobalCycleDetectionEnabled) { if (isGlobalCycleDetectionEnabled) {
@@ -223,6 +238,7 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
return withCycleDetection<T>(T, named, () { return withCycleDetection<T>(T, named, () {
var resolved = _tryResolveInternal<T>(named: named, params: params); var resolved = _tryResolveInternal<T>(named: named, params: params);
if (resolved != null) { if (resolved != null) {
observer.onInstanceCreated(T.toString(), T, resolved, scopeName: scopeId);
observer.onDiagnostic( observer.onDiagnostic(
'Successfully resolved: $T', 'Successfully resolved: $T',
details: { details: {
@@ -316,8 +332,24 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
return withCycleDetection<Future<T>>(T, named, () async { return withCycleDetection<Future<T>>(T, named, () async {
var resolved = await _tryResolveAsyncInternal<T>(named: named, params: params); var resolved = await _tryResolveAsyncInternal<T>(named: named, params: params);
if (resolved != null) { if (resolved != null) {
observer.onInstanceCreated(T.toString(), T, resolved, scopeName: scopeId);
observer.onDiagnostic(
'Successfully async resolved: $T',
details: {
'type': 'Scope',
'name': scopeId,
'resolve': T.toString(),
if (named != null) 'named': named,
'description': 'successfully resolved (async)',
},
);
return resolved; return resolved;
} else { } else {
observer.onError(
'Failed to async resolve: $T',
null,
null,
);
throw StateError( throw StateError(
'Can\'t resolve async dependency `$T`. Maybe you forget register it?'); 'Can\'t resolve async dependency `$T`. Maybe you forget register it?');
} }