2256_chore/fix-gatewayservice-uninitialized-services#2514
Conversation
73ad5a0 to
5dc5bbb
Compare
Review and Fixes AppliedRebased onto Issues Found and Fixed
Verification
Design ReviewThe approach is sound:
|
Refactors GatewayService, ExportService, ImportService, and A2AService to use globally-initialized service singletons (ToolService, PromptService, ResourceService, ServerService, RootService, GatewayService) instead of creating private, uninitialized instances. Uses lazy singleton pattern with __getattr__ to avoid import-time instantiation when only exception classes are imported. This ensures services are created after logging/plugin setup is complete. By importing the module-level services, all gateway operations now share the same EventService/Redis client. This ensures events such as activate/deactivate propagate correctly across workers and reach Redis subscribers. Changes: - Add lazy singleton pattern using __getattr__ to service modules - Update main.py to import singletons instead of instantiating services - Update GatewayService.__init__ to use lazy imports of singletons - Update ExportService.__init__ to use lazy imports of singletons - Update ImportService.__init__ to use lazy imports of singletons - Update A2AService methods to use tool_service singleton - Update tests to patch singleton methods instead of class instantiation - Add pylint disables for no-name-in-module (due to __getattr__) The fix resolves silent event drops caused by missing initialize() calls on locally constructed services. Cross-worker UI updates and subscriber notifications now behave as intended. Closes #2256 Signed-off-by: NAYANAR <nayana.r7813@gmail.com> Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
5dc5bbb to
8e964d2
Compare
Additional Review: Import-Time Instantiation FixAfter reviewing feedback about import-time side effects, I've updated the implementation to use lazy singleton pattern with Issues Identified
Solution AppliedChanged from eager instantiation: # OLD - instantiated at module import time
tool_service = ToolService()To lazy singleton pattern: # NEW - instantiated on first access
_tool_service_instance = None
def __getattr__(name: str):
global _tool_service_instance
if name == "tool_service":
if _tool_service_instance is None:
_tool_service_instance = ToolService()
return _tool_service_instance
raise AttributeError(...)Benefits
Test UpdatesUpdated tests in # OLD - patched class that was no longer used
with patch("mcpgateway.services.a2a_service.ToolService") as mock:
# NEW - patch method on actual singleton
from mcpgateway.services.tool_service import tool_service
with patch.object(tool_service, "create_tool_from_a2a_agent", ...):Verification
|
IBM#2514) Refactors GatewayService, ExportService, ImportService, and A2AService to use globally-initialized service singletons (ToolService, PromptService, ResourceService, ServerService, RootService, GatewayService) instead of creating private, uninitialized instances. Uses lazy singleton pattern with __getattr__ to avoid import-time instantiation when only exception classes are imported. This ensures services are created after logging/plugin setup is complete. By importing the module-level services, all gateway operations now share the same EventService/Redis client. This ensures events such as activate/deactivate propagate correctly across workers and reach Redis subscribers. Changes: - Add lazy singleton pattern using __getattr__ to service modules - Update main.py to import singletons instead of instantiating services - Update GatewayService.__init__ to use lazy imports of singletons - Update ExportService.__init__ to use lazy imports of singletons - Update ImportService.__init__ to use lazy imports of singletons - Update A2AService methods to use tool_service singleton - Update tests to patch singleton methods instead of class instantiation - Add pylint disables for no-name-in-module (due to __getattr__) The fix resolves silent event drops caused by missing initialize() calls on locally constructed services. Cross-worker UI updates and subscriber notifications now behave as intended. Closes IBM#2256 Signed-off-by: NAYANAR <nayana.r7813@gmail.com> Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Co-authored-by: Mihai Criveti <crivetimihai@gmail.com> Signed-off-by: hughhennnelly <hughhennelly06@gmail.com>
IBM#2514) Refactors GatewayService, ExportService, ImportService, and A2AService to use globally-initialized service singletons (ToolService, PromptService, ResourceService, ServerService, RootService, GatewayService) instead of creating private, uninitialized instances. Uses lazy singleton pattern with __getattr__ to avoid import-time instantiation when only exception classes are imported. This ensures services are created after logging/plugin setup is complete. By importing the module-level services, all gateway operations now share the same EventService/Redis client. This ensures events such as activate/deactivate propagate correctly across workers and reach Redis subscribers. Changes: - Add lazy singleton pattern using __getattr__ to service modules - Update main.py to import singletons instead of instantiating services - Update GatewayService.__init__ to use lazy imports of singletons - Update ExportService.__init__ to use lazy imports of singletons - Update ImportService.__init__ to use lazy imports of singletons - Update A2AService methods to use tool_service singleton - Update tests to patch singleton methods instead of class instantiation - Add pylint disables for no-name-in-module (due to __getattr__) The fix resolves silent event drops caused by missing initialize() calls on locally constructed services. Cross-worker UI updates and subscriber notifications now behave as intended. Closes IBM#2256 Signed-off-by: NAYANAR <nayana.r7813@gmail.com> Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
Signed-off-by: NAYANAR nayana.r7813@gmail.com
Closes #2256
This change refactors GatewayService to reuse the globally-initialized service singletons (ToolService, PromptService, ResourceService) instead of creating private, uninitialized instances.
By importing the module-level services created and initialized in mcpgateway.main, all gateway operations now share the same EventService/Redis client.
This ensures events such as activate/deactivate propagate correctly across workers and reach Redis subscribers.
The fix resolves silent event drops caused by missing initialize() calls on locally constructed services.
Lazy imports are used to avoid circular dependencies while preserving correct initialization order.
Cross-worker UI updates and subscriber notifications now behave as intended.
Teating
python - <<'PY'import asynciofrom mcpgateway.services.tool_service import tool_serviceasync def test(): async def sub(): async for ev in tool_service.subscribe_events(): print('RECEIVED_EVENT', ev) return async def pub(): await asyncio.sleep(0.2) await tool_service._publish_event({'type': 'test_event', 'data': {'ok': True}}) await asyncio.gather(sub(), pub())asyncio.run(test())PY
python - <<'PY'import mcpgateway.main as mainfrom mcpgateway.services.tool_service import tool_service as svcprint('tool_service is main.tool_service ->', svc is main.tool_service)PY