We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/jmagar/homelab-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
# ServiceContainer Lifecycle Management
## Overview
ServiceContainer now includes comprehensive lifecycle management for coordinating service initialization, health monitoring, and graceful shutdown.
## Lifecycle States
```
CREATED → INITIALIZING → READY → SHUTTING_DOWN → SHUTDOWN
↑ ↓
└──────────── (on error) ──────────────┘
```
| State | Description | Operations Allowed |
| --------------- | ------------------------------------------------ | --------------------------------------------- |
| `CREATED` | Container instantiated, services not initialized | `initialize()` |
| `INITIALIZING` | Async initialization in progress | Wait for completion |
| `READY` | Fully initialized, accepting requests | All operations, `healthCheck()`, `shutdown()` |
| `SHUTTING_DOWN` | Graceful shutdown in progress | Wait for completion |
| `SHUTDOWN` | Fully stopped, resources released | None |
## API
### initialize()
Perform async service setup and preload configurations.
```typescript
const container = new ServiceContainer()
await container.initialize()
// Container is now ready
console.log(container.isReady()) // true
```
**What it does:**
1. Transitions to `INITIALIZING` state
2. Preloads host configurations (enables caching)
3. Emits lifecycle events
4. Transitions to `READY` state
**Throws:**
- If already initialized or shutting down
- If initialization fails (resets to `CREATED`)
### healthCheck()
Query health status of all initialized services.
```typescript
const health = await container.healthCheck()
for (const service of health) {
console.log(`${service.name}: ${service.healthy ? "✓" : "✗"}`)
if (!service.healthy) {
console.error(` Error: ${service.message}`)
}
if (service.responseTime) {
console.log(` Response time: ${service.responseTime}ms`)
}
}
```
**Returns:** `ServiceContainerHealth[]`
```typescript
interface ServiceContainerHealth {
name: string // Service name
healthy: boolean // Health status
message?: string // Error message if unhealthy
responseTime?: number // Response time in ms (for async checks)
}
```
**Checked Services:**
- `SSHConnectionPool` - Connection health, circuit breaker status
- `HostConfigRepository` - Can load host configs
- `DockerService` - Service instantiated
- `EventEmitter` - Active listener count
### shutdown(timeout?)
Gracefully shut down with timeout protection.
```typescript
// Default 30s timeout
await container.shutdown()
// Custom timeout
await container.shutdown(10000) // 10 seconds
```
**What it does:**
1. Transitions to `SHUTTING_DOWN` state
2. Closes all SSH connections
3. Clears Docker clients
4. Clears host config cache
5. Removes all event listeners
6. Transitions to `SHUTDOWN` state
**Parameters:**
- `timeout` (optional): Max shutdown time in milliseconds (default: 30000)
**Throws:**
- If shutdown times out (but still transitions to `SHUTDOWN`)
- If shutdown already in progress
**Idempotent:** Safe to call multiple times (no-op if already shut down)
### getLifecycleState()
Get current lifecycle state.
```typescript
const state = container.getLifecycleState()
console.log(state) // "ready", "shutting_down", etc.
```
### isReady()
Quick check if container is ready to accept requests.
```typescript
if (container.isReady()) {
// Safe to use services
const hosts = await container.getHostConfigRepository().loadHosts()
}
```
## Usage Patterns
### Pattern 1: Application Startup
```typescript
import { ServiceContainer } from "./services/container.js"
async function main() {
const container = new ServiceContainer()
try {
// Initialize with preloading
await container.initialize()
console.log("Container ready")
// Register handlers, start server, etc.
// ...
} catch (error) {
console.error("Initialization failed:", error)
await container.cleanup()
process.exit(1)
}
}
main()
```
### Pattern 2: Health Check Endpoint
```typescript
app.get("/health", async (req, res) => {
const health = await container.healthCheck()
const allHealthy = health.every((s) => s.healthy)
const status = allHealthy ? 200 : 503
res.status(status).json({
status: allHealthy ? "healthy" : "unhealthy",
services: health
})
})
```
### Pattern 3: Graceful Shutdown
```typescript
const container = new ServiceContainer()
await container.initialize()
// Handle shutdown signals
process.on("SIGTERM", async () => {
console.log("SIGTERM received, shutting down gracefully")
try {
await container.shutdown(30000) // 30s timeout
console.log("Shutdown complete")
process.exit(0)
} catch (error) {
console.error("Shutdown timeout, forcing exit")
process.exit(1)
}
})
process.on("SIGINT", async () => {
console.log("SIGINT received, shutting down gracefully")
await container.shutdown(10000) // 10s timeout for Ctrl+C
process.exit(0)
})
```
### Pattern 4: Readiness Gate
```typescript
class Server {
constructor(private container: ServiceContainer) {}
async handleRequest(req, res) {
// Check readiness before processing
if (!this.container.isReady()) {
return res.status(503).json({ error: "Service unavailable" })
}
// Process request
const dockerService = this.container.getDockerService()
// ...
}
}
```
### Pattern 5: Startup Health Validation
```typescript
async function startWithValidation() {
const container = new ServiceContainer()
await container.initialize()
// Verify all services healthy before starting
const health = await container.healthCheck()
const critical = health.filter((s) => !s.healthy)
if (critical.length > 0) {
console.error("Critical services unhealthy:")
for (const service of critical) {
console.error(` ${service.name}: ${service.message}`)
}
await container.shutdown()
throw new Error("Health check failed")
}
console.log("All services healthy, starting server")
// Start server...
}
```
## Events
Lifecycle transitions emit events through the EventEmitter:
```typescript
const eventEmitter = container.getEventEmitter()
eventEmitter.on("container_state_changed", (event) => {
console.log(`Container ${event.action} at ${event.timestamp}`)
})
await container.initialize() // Emits start event
await container.shutdown() // Emits stop events
```
## Migration from Cleanup
**Old Code:**
```typescript
// Immediate cleanup, no lifecycle awareness
await container.cleanup()
```
**New Code:**
```typescript
// Graceful shutdown with timeout
await container.shutdown(30000)
// Or for backward compatibility, cleanup still works
await container.cleanup()
```
## Best Practices
### DO
✅ **Initialize on startup**
```typescript
await container.initialize()
```
✅ **Check readiness before use**
```typescript
if (container.isReady()) {
// Use services
}
```
✅ **Use shutdown for graceful termination**
```typescript
await container.shutdown()
```
✅ **Handle shutdown signals**
```typescript
process.on("SIGTERM", () => container.shutdown())
```
✅ **Run health checks periodically**
```typescript
setInterval(async () => {
const health = await container.healthCheck()
// Log or report health
}, 60000)
```
### DON'T
❌ **Use services before initialization**
```typescript
// Bad: No initialization
const hosts = await container.getHostConfigRepository().loadHosts()
// Good: Initialize first
await container.initialize()
const hosts = await container.getHostConfigRepository().loadHosts()
```
❌ **Ignore lifecycle state**
```typescript
// Bad: Assume always ready
const service = container.getDockerService()
// Good: Check readiness
if (!container.isReady()) {
throw new Error("Container not ready")
}
const service = container.getDockerService()
```
❌ **Skip shutdown on exit**
```typescript
// Bad: Process exits without cleanup
process.exit(0)
// Good: Graceful shutdown first
await container.shutdown()
process.exit(0)
```
❌ **Infinite shutdown timeout**
```typescript
// Bad: No timeout protection
await container.shutdown(Number.MAX_SAFE_INTEGER)
// Good: Reasonable timeout
await container.shutdown(30000)
```
## Troubleshooting
### Container stuck in INITIALIZING
**Symptom:** `initialize()` never completes
**Cause:** Host config loading hangs (network issue, SSH timeout)
**Solution:**
```typescript
// Add initialization timeout
const initTimeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error("Init timeout")), 10000)
)
await Promise.race([container.initialize(), initTimeout])
```
### Health check shows unhealthy services
**Symptom:** `healthCheck()` returns `healthy: false`
**Debug:**
```typescript
const health = await container.healthCheck()
const unhealthy = health.filter((s) => !s.healthy)
for (const service of unhealthy) {
console.error(`${service.name}: ${service.message}`)
// Inspect service configuration
}
```
### Shutdown timeout
**Symptom:** `shutdown()` throws timeout error
**Cause:** Service taking too long to close connections
**Solution:**
```typescript
try {
await container.shutdown(30000)
} catch (error) {
if (error.message.includes("timed out")) {
console.warn("Shutdown timeout, forcing cleanup")
// Container is already in SHUTDOWN state
process.exit(1)
}
}
```
## Testing
### Unit Testing with Lifecycle
```typescript
import { describe, it, expect, beforeEach, afterEach } from "vitest"
import { ServiceContainer } from "./container.js"
describe("MyFeature", () => {
let container: ServiceContainer
beforeEach(async () => {
container = new ServiceContainer()
await container.initialize()
})
afterEach(async () => {
await container.shutdown(1000) // Quick shutdown for tests
})
it("should do something", async () => {
expect(container.isReady()).toBe(true)
// Test your feature...
})
})
```
### Mocking Services
```typescript
const mockDockerService = {
listContainers: vi.fn().mockResolvedValue([])
}
const container = new ServiceContainer()
container.setDockerService(mockDockerService)
// No need to initialize for unit tests with mocked services
```
## References
- Implementation: `src/services/container.ts`
- Tests: `src/services/container-lifecycle.test.ts`
- Event system: `src/events/README.md`