Skip to main content
Glama
portel-dev

NCP - Natural Context Provider

by portel-dev
process-isolation.test.ts7.56 kB
/** * Process Isolation Tests * Verify that tasks execute in isolated child processes * and one crashing task doesn't affect others */ import { describe, it, expect, beforeEach, afterEach } from '@jest/globals'; import { createTestDirectory, cleanupTestDirectory, mockSchedulerEnvironment } from './test-helpers'; import { v4 as uuidv4 } from 'uuid'; import { ScheduledTask } from '../../src/types/scheduler'; import { TaskManager } from '../../src/services/scheduler/task-manager'; import { TimingExecutor } from '../../src/services/scheduler/timing-executor'; describe('Process Isolation Tests', () => { let testDir: string; let schedulerDir: string; beforeEach(() => { // Create isolated test environment testDir = createTestDirectory(); const env = mockSchedulerEnvironment(testDir); schedulerDir = env.schedulerDir; }); afterEach(() => { cleanupTestDirectory(testDir); }); it('should demonstrate child process isolation concept', () => { // This test verifies the architecture, not actual execution // Actual execution would require: // 1. Real MCP tools to execute // 2. NCP to be fully installed // 3. OS scheduler setup const taskManager = new TaskManager(); const timingExecutor = new TimingExecutor(); // Verify the executor has the child process method expect(timingExecutor).toBeDefined(); expect((timingExecutor as any).executeTaskInChildProcess).toBeDefined(); // The key architecture points: // 1. Each task spawns: `ncp _task-execute <taskId>` // 2. Uses child_process.spawn() for true isolation // 3. If one task crashes, it's in its own process // 4. Promise.allSettled ensures other tasks continue expect(true).toBe(true); }); it('should verify timing group data structure supports multiple tasks', () => { const taskManager = new TaskManager(schedulerDir); const timestamp = Date.now(); // Create a timing group const cronExpression = '0 9 * * *'; const timingId = taskManager.getOrCreateTimingGroup(cronExpression); // Verify timing was created and starts empty const timing = taskManager.getTimingGroup(timingId); expect(timing).toBeDefined(); expect(timing?.cronExpression).toBe(cronExpression); expect(timing?.taskIds).toEqual([]); // Create multiple tasks sharing the same timing (use timestamp for unique names) const task1: ScheduledTask = { id: uuidv4(), name: `Task 1 ${timestamp}`, timingId, cronExpression, tool: 'test:tool1', parameters: {}, fireOnce: false, createdAt: new Date().toISOString(), status: 'active', executionCount: 0 }; const task2: ScheduledTask = { id: uuidv4(), name: `Task 2 ${timestamp}`, timingId, cronExpression, tool: 'test:tool2', parameters: {}, fireOnce: false, createdAt: new Date().toISOString(), status: 'active', executionCount: 0 }; const task3: ScheduledTask = { id: uuidv4(), name: `Task 3 (Paused) ${timestamp}`, timingId, cronExpression, tool: 'test:tool3', parameters: {}, fireOnce: false, createdAt: new Date().toISOString(), status: 'paused', // This one is paused executionCount: 0 }; // Add tasks to storage taskManager.createTask(task1); taskManager.createTask(task2); taskManager.createTask(task3); // Verify timing has all task IDs const updatedTiming = taskManager.getTimingGroup(timingId); expect(updatedTiming?.taskIds).toHaveLength(3); expect(updatedTiming?.taskIds).toContain(task1.id); expect(updatedTiming?.taskIds).toContain(task2.id); expect(updatedTiming?.taskIds).toContain(task3.id); // Verify we can get active tasks only const activeTasks = taskManager.getActiveTasksForTiming(timingId); expect(activeTasks).toHaveLength(2); // Only task1 and task2, not the paused one expect(activeTasks.find(t => t.id === task1.id)).toBeDefined(); expect(activeTasks.find(t => t.id === task2.id)).toBeDefined(); expect(activeTasks.find(t => t.id === task3.id)).toBeUndefined(); // Paused task not included }); it('should demonstrate isolation benefits', () => { // Key benefits of child process isolation: // 1. Memory Isolation // Each task runs in its own process with its own heap // A memory leak in one task doesn't affect others // 2. Crash Isolation // If one task has uncaught exception → only that process dies // Other tasks in separate processes continue normally // 3. CPU Isolation // Each process has its own event loop // CPU-intensive task in one process doesn't block others // 4. Resource Limits // Can set per-process limits (memory, CPU time) // Prevents one bad task from consuming all resources // 5. Timeout Control // Can kill individual process if it exceeds timeout // Uses SIGTERM then SIGKILL if needed expect(true).toBe(true); }); it('should handle timing group cleanup when last task is deleted', () => { const taskManager = new TaskManager(schedulerDir); // Create timing with one task const cronExpression = '0 10 * * *'; const timingId = taskManager.getOrCreateTimingGroup(cronExpression); const task: ScheduledTask = { id: uuidv4(), name: 'Only Task', timingId, cronExpression, tool: 'test:tool', parameters: {}, fireOnce: false, createdAt: new Date().toISOString(), status: 'active', executionCount: 0 }; taskManager.createTask(task); // Verify timing exists expect(taskManager.getTimingGroup(timingId)).toBeDefined(); // Delete the task const shouldRemoveTiming = taskManager.deleteTask(task.id); // Should return true because this was the last task expect(shouldRemoveTiming).toBe(true); // Timing should be deleted expect(taskManager.getTimingGroup(timingId)).toBeNull(); }); it('should NOT delete timing group when other tasks still use it', () => { const taskManager = new TaskManager(schedulerDir); const timestamp = Date.now(); // Create timing with multiple tasks const cronExpression = '0 11 * * *'; const timingId = taskManager.getOrCreateTimingGroup(cronExpression); const task1: ScheduledTask = { id: uuidv4(), name: `Task 1 ${timestamp}`, timingId, cronExpression, tool: 'test:tool1', parameters: {}, fireOnce: false, createdAt: new Date().toISOString(), status: 'active', executionCount: 0 }; const task2: ScheduledTask = { id: uuidv4(), name: `Task 2 ${timestamp}`, timingId, cronExpression, tool: 'test:tool2', parameters: {}, fireOnce: false, createdAt: new Date().toISOString(), status: 'active', executionCount: 0 }; taskManager.createTask(task1); taskManager.createTask(task2); // Delete only one task const shouldRemoveTiming = taskManager.deleteTask(task1.id); // Should return false because task2 still uses this timing expect(shouldRemoveTiming).toBe(false); // Timing should still exist expect(taskManager.getTimingGroup(timingId)).toBeDefined(); // Should have one task remaining const remainingTiming = taskManager.getTimingGroup(timingId); expect(remainingTiming?.taskIds).toHaveLength(1); expect(remainingTiming?.taskIds).toContain(task2.id); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/portel-dev/ncp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server