UNPKG

@agility/cli

Version:

Agility CLI for working with your content. (Public Beta)

1,034 lines (835 loc) β€’ 37.3 kB
# Parallel Execution Plan & Thread Coordination Strategy **Phase 19 - Task 19.1.3 Implementation** **Status**: βœ… **FOUNDATION VALIDATED** - Ready for Implementation **Created**: Phase 19 Development Session **Validated**: Phase 19.5-19.6 - 100% entity reconciliation across 24,164+ entities from 3 customer instances --- ## πŸŽ‰ **VALIDATION SUCCESS SUMMARY** **Foundation Proven Ready:** - βœ… **Modular service architecture** - 12 focused components ready for parallel coordination - βœ… **Perfect entity analysis** - 6-step dependency analysis working flawlessly - βœ… **Dependency coordination** - Complex relationship resolution validated across 24,164+ entities - βœ… **Error handling proven** - Broken chain detection and recovery strategies working - βœ… **Real-world validation** - Tested across 3 diverse customer production instances **Implementation Readiness:** - βœ… **Service coordination proven** - AnalysisStepCoordinator and ComprehensiveAnalysisRunner working - βœ… **Thread-safe patterns** - Modular architecture supports parallel execution - βœ… **Progress tracking foundation** - Real-time progress reporting established - βœ… **Error resilience** - Comprehensive error detection and reporting working --- ## 🎯 Strategic Objective Design and implement ultra-high parallelism upload system with dynamic thread pools, adaptive concurrency, and real-time progress tracking to maximize throughput while maintaining data integrity and dependency relationships. **Foundation**: Built on proven modular service architecture that has achieved 100% accuracy across diverse real-world instances with perfect coordination between 12 service components. --- ## 🧡 Thread Architecture Overview ### **Multi-Threaded Upload Strategy** ```typescript interface ThreadConfiguration { threadId: string; threadType: 'independent' | 'dependency' | 'complex'; priority: number; dependsOn: string[]; entities: string[]; estimatedDuration: number; } class ParallelExecutionManager { private threads: Map<string, UploadThread> = new Map(); private progressTracker: GlobalProgressTracker; private dependencyGates: Map<string, DependencyGate> = new Map(); private idMappingService: IdMappingService; private errorHandler: ParallelErrorHandler; constructor() { this.setupThreadConfiguration(); this.initializeDependencyGates(); this.progressTracker = new GlobalProgressTracker(); this.idMappingService = new IdMappingService(); this.errorHandler = new ParallelErrorHandler(); } /** * Configure the three main upload threads */ private setupThreadConfiguration(): void { const config: ThreadConfiguration[] = [ { threadId: 'independent-entities', threadType: 'independent', priority: 1, dependsOn: [], entities: ['models', 'templates', 'containers', 'independent-assets'], estimatedDuration: 30000 // 30 seconds }, { threadId: 'batched-content', threadType: 'dependency', priority: 2, dependsOn: ['models-complete', 'templates-complete', 'containers-complete'], entities: ['content-levels'], estimatedDuration: 120000 // 2 minutes }, { threadId: 'complex-entities', threadType: 'complex', priority: 3, dependsOn: ['content-complete', 'assets-complete'], entities: ['pages', 'galleries'], estimatedDuration: 90000 // 1.5 minutes } ]; config.forEach(threadConfig => { this.threads.set(threadConfig.threadId, new UploadThread(threadConfig)); }); } } ``` --- ## πŸ”„ Thread 1: Independent Entity Upload ### **Parallel Independent Operations** ```typescript class IndependentEntityThread extends UploadThread { async execute(sourceEntities: SourceEntities): Promise<ThreadResult> { const startTime = Date.now(); const results: ThreadResult = { threadId: 'independent-entities', successful: [], failed: [], totalTime: 0, subResults: new Map() }; console.log(`\nπŸš€ Thread 1: Independent Entity Upload Started`); console.log(`πŸ“Š Entities: ${sourceEntities.models?.length || 0} models, ${sourceEntities.templates?.length || 0} templates, ${sourceEntities.containers?.length || 0} containers`); // Execute independent operations in parallel const parallelTasks = [ this.uploadModels(sourceEntities.models || []), this.uploadTemplates(sourceEntities.templates || []), this.uploadIndependentAssets(sourceEntities.assets || []) ]; try { const [modelResults, templateResults, assetResults] = await Promise.all(parallelTasks); // Merge results results.subResults.set('models', modelResults); results.subResults.set('templates', templateResults); results.subResults.set('assets', assetResults); // Upload containers after models are complete (dependency within thread) const containerResults = await this.uploadContainers(sourceEntities.containers || []); results.subResults.set('containers', containerResults); // Signal completion gates await this.signalGate('models-complete'); await this.signalGate('templates-complete'); await this.signalGate('containers-complete'); await this.signalGate('independent-assets-complete'); } catch (error) { this.errorHandler.handleThreadError('independent-entities', error); throw error; } results.totalTime = Date.now() - startTime; console.log(`βœ… Thread 1: Independent Entity Upload Complete (${results.totalTime}ms)`); return results; } /** * Upload models sequentially (no batch method available) */ private async uploadModels(models: ModelItem[]): Promise<SubThreadResult> { const result: SubThreadResult = { uploaded: [], failed: [], totalTime: 0 }; const startTime = Date.now(); console.log(` πŸ“‹ Uploading ${models.length} models sequentially...`); for (let i = 0; i < models.length; i++) { const model = models[i]; try { // Progress update this.progressTracker.updateSubProgress('models', i + 1, models.length); // Mock API call - replace with actual Management SDK saveModel() const newModelId = await this.mockSaveModel(model); // Update ID mapping this.idMappingService.mapEntity('model', model.id, newModelId); result.uploaded.push({ sourceId: model.id, targetId: newModelId }); console.log(` βœ“ Model ${model.referenceName} β†’ ID ${newModelId}`); } catch (error) { result.failed.push({ sourceId: model.id, error: error.message }); console.log(` ❌ Model ${model.referenceName} failed: ${error.message}`); } } result.totalTime = Date.now() - startTime; console.log(` βœ… Models complete: ${result.uploaded.length} uploaded, ${result.failed.length} failed`); return result; } /** * Upload templates sequentially (depends on models) */ private async uploadTemplates(templates: TemplateItem[]): Promise<SubThreadResult> { const result: SubThreadResult = { uploaded: [], failed: [], totalTime: 0 }; const startTime = Date.now(); console.log(` πŸ—οΈ Uploading ${templates.length} templates sequentially...`); for (let i = 0; i < templates.length; i++) { const template = templates[i]; try { // Update template references with mapped model IDs const updatedTemplate = this.updateTemplateModelReferences(template); // Progress update this.progressTracker.updateSubProgress('templates', i + 1, templates.length); // Mock API call - replace with actual Management SDK saveTemplate() const newTemplateId = await this.mockSaveTemplate(updatedTemplate); // Update ID mapping this.idMappingService.mapEntity('template', template.id, newTemplateId); result.uploaded.push({ sourceId: template.id, targetId: newTemplateId }); console.log(` βœ“ Template ${template.pageTemplateName} β†’ ID ${newTemplateId}`); } catch (error) { result.failed.push({ sourceId: template.id, error: error.message }); console.log(` ❌ Template ${template.pageTemplateName} failed: ${error.message}`); } } result.totalTime = Date.now() - startTime; console.log(` βœ… Templates complete: ${result.uploaded.length} uploaded, ${result.failed.length} failed`); return result; } /** * Upload independent assets using batch upload */ private async uploadIndependentAssets(assets: AssetItem[]): Promise<SubThreadResult> { const independentAssets = assets.filter(asset => !asset.mediaGroupingID); const result: SubThreadResult = { uploaded: [], failed: [], totalTime: 0 }; const startTime = Date.now(); if (independentAssets.length === 0) { console.log(` πŸ“Ž No independent assets to upload`); return result; } console.log(` πŸ“Ž Uploading ${independentAssets.length} independent assets in batches...`); const batchUploader = new AssetBatchUploader(); const strategy = batchUploader.analyzeAssetBatching(independentAssets); const uploadResult = await batchUploader.uploadAssetBatches(strategy); // Convert to SubThreadResult format result.uploaded = uploadResult.uploadedAssets.map(asset => ({ sourceId: asset.sourceId, targetId: asset.targetId })); result.failed = uploadResult.failedAssets.map(asset => ({ sourceId: asset.asset.mediaID, error: asset.error })); result.totalTime = Date.now() - startTime; console.log(` βœ… Independent assets complete: ${result.uploaded.length} uploaded, ${result.failed.length} failed`); return result; } private updateTemplateModelReferences(template: TemplateItem): TemplateItem { // Deep clone and update model ID references const updated = JSON.parse(JSON.stringify(template)); // Update contentSectionDefinitions with mapped model IDs if (updated.contentSectionDefinitions) { updated.contentSectionDefinitions.forEach((section: any) => { if (section.contentDefinitionID) { const mappedId = this.idMappingService.getMappedId('model', section.contentDefinitionID); if (mappedId) { section.contentDefinitionID = mappedId; } } }); } return updated; } private async mockSaveModel(model: ModelItem): Promise<number> { await this.delay(150); // Simulate API call return 50000 + model.id; // Mock new ID } private async mockSaveTemplate(template: TemplateItem): Promise<number> { await this.delay(200); // Simulate API call return 60000 + template.id; // Mock new ID } private delay(ms: number): Promise<void> { return new Promise(resolve => setTimeout(resolve, ms)); } } ``` --- ## πŸ”— Thread 2: Dependency-Level Content Upload ### **Batched Content Processing with Dependencies** ```typescript class DependencyContentThread extends UploadThread { private contentAnalyzer: ContentDependencyAnalyzer; private batchUploader: DependencyLevelBatchUploader; constructor(config: ThreadConfiguration) { super(config); this.contentAnalyzer = new ContentDependencyAnalyzer(); this.batchUploader = new DependencyLevelBatchUploader({ maxBatchSize: 50, maxConcurrentBatches: 3, retryAttempts: 3, retryDelayMs: 1000 }); } async execute(sourceEntities: SourceEntities): Promise<ThreadResult> { const startTime = Date.now(); const results: ThreadResult = { threadId: 'batched-content', successful: [], failed: [], totalTime: 0, subResults: new Map() }; // Wait for required dependencies console.log(`\n⏳ Thread 2: Waiting for dependencies...`); await this.waitForGate('models-complete'); await this.waitForGate('templates-complete'); await this.waitForGate('containers-complete'); console.log(`\nπŸ”— Thread 2: Dependency-Level Content Upload Started`); try { // Analyze content dependencies const dependencyLevels = this.contentAnalyzer.analyzeDependencyLevels(sourceEntities.content || []); console.log(`πŸ“Š Content Analysis: ${dependencyLevels.size} dependency levels identified`); dependencyLevels.forEach((deps, level) => { console.log(` Level ${level}: ${deps.length} content items`); }); // Upload content by dependency levels const contentResult = await this.batchUploader.uploadContentByDependencyLevels( dependencyLevels, sourceEntities ); results.subResults.set('content', { uploaded: contentResult.successful.map(s => ({ sourceId: s.sourceId, targetId: s.targetId })), failed: contentResult.failed.map(f => ({ sourceId: f.sourceId, error: f.error })), totalTime: contentResult.totalTime }); // Signal completion await this.signalGate('content-complete'); } catch (error) { this.errorHandler.handleThreadError('batched-content', error); throw error; } results.totalTime = Date.now() - startTime; console.log(`βœ… Thread 2: Content Upload Complete (${results.totalTime}ms)`); return results; } } ``` --- ## πŸ—οΈ Thread 3: Complex Entity Chain Traversal ### **Page and Gallery Chain Processing** ```typescript class ComplexEntityThread extends UploadThread { private pageChainUploader: PageChainUploader; private galleryChainUploader: GalleryChainUploader; constructor(config: ThreadConfiguration) { super(config); this.pageChainUploader = new PageChainUploader(); this.galleryChainUploader = new GalleryChainUploader(); } async execute(sourceEntities: SourceEntities): Promise<ThreadResult> { const startTime = Date.now(); const results: ThreadResult = { threadId: 'complex-entities', successful: [], failed: [], totalTime: 0, subResults: new Map() }; // Wait for required dependencies console.log(`\n⏳ Thread 3: Waiting for dependencies...`); await this.waitForGate('content-complete'); await this.waitForGate('independent-assets-complete'); console.log(`\nπŸ—οΈ Thread 3: Complex Entity Chain Traversal Started`); try { // Process gallery-dependent assets first const galleryAssets = (sourceEntities.assets || []).filter(asset => asset.mediaGroupingID); if (galleryAssets.length > 0) { console.log(`πŸ–ΌοΈ Uploading ${galleryAssets.length} gallery assets...`); const galleryAssetResult = await this.uploadGalleryAssets(galleryAssets); results.subResults.set('gallery-assets', galleryAssetResult); } // Process pages and galleries in parallel const [pageResults, galleryResults] = await Promise.all([ this.uploadPageChains(sourceEntities.pages || []), this.uploadGalleries(sourceEntities.galleries || []) ]); results.subResults.set('pages', pageResults); results.subResults.set('galleries', galleryResults); // Signal completion await this.signalGate('all-entities-complete'); } catch (error) { this.errorHandler.handleThreadError('complex-entities', error); throw error; } results.totalTime = Date.now() - startTime; console.log(`βœ… Thread 3: Complex Entity Upload Complete (${results.totalTime}ms)`); return results; } /** * Upload gallery assets in gallery-grouped batches */ private async uploadGalleryAssets(galleryAssets: AssetItem[]): Promise<SubThreadResult> { const galleryGroups = this.groupAssetsByGallery(galleryAssets); const result: SubThreadResult = { uploaded: [], failed: [], totalTime: 0 }; const startTime = Date.now(); for (const [galleryId, assets] of galleryGroups) { console.log(` πŸ“¦ Gallery ${galleryId}: ${assets.length} assets`); try { // Mock batch upload for gallery const uploadResult = await this.mockUploadGalleryAssets(assets, galleryId); result.uploaded.push(...uploadResult.uploaded); result.failed.push(...uploadResult.failed); // Update ID mappings uploadResult.uploaded.forEach(asset => { this.idMappingService.mapEntity('asset', asset.sourceId, asset.targetId); }); } catch (error) { result.failed.push(...assets.map(asset => ({ sourceId: asset.mediaID, error: error.message }))); } } result.totalTime = Date.now() - startTime; return result; } /** * Upload page chains using chain traversal */ private async uploadPageChains(pages: PageItem[]): Promise<SubThreadResult> { const result: SubThreadResult = { uploaded: [], failed: [], totalTime: 0 }; const startTime = Date.now(); console.log(` πŸ“„ Processing ${pages.length} pages via chain traversal...`); // Group pages by hierarchy (using existing hierarchy logic) const pageChains = this.buildPageChains(pages); for (let i = 0; i < pageChains.length; i++) { const chain = pageChains[i]; console.log(` Chain ${i + 1}/${pageChains.length}: ${chain.pages.length} pages`); try { const chainResult = await this.pageChainUploader.uploadChain(chain); result.uploaded.push(...chainResult.uploaded); result.failed.push(...chainResult.failed); this.progressTracker.updateSubProgress('pages', i + 1, pageChains.length); } catch (error) { console.log(` ❌ Chain ${i + 1} failed: ${error.message}`); result.failed.push(...chain.pages.map(page => ({ sourceId: page.pageID, error: error.message }))); } } result.totalTime = Date.now() - startTime; console.log(` βœ… Page chains complete: ${result.uploaded.length} uploaded, ${result.failed.length} failed`); return result; } private groupAssetsByGallery(assets: AssetItem[]): Map<number, AssetItem[]> { const groups = new Map<number, AssetItem[]>(); assets.forEach(asset => { if (asset.mediaGroupingID) { if (!groups.has(asset.mediaGroupingID)) { groups.set(asset.mediaGroupingID, []); } groups.get(asset.mediaGroupingID)!.push(asset); } }); return groups; } private buildPageChains(pages: PageItem[]): PageChain[] { // Build page chains based on dependency analysis // This would use the existing page chain analysis logic return []; // Placeholder } } ``` --- ## πŸ“Š Global Progress Tracking System ### **Multi-Thread Progress Coordination** ```typescript interface GlobalProgressState { threads: Map<string, ThreadProgress>; overallProgress: number; totalItems: number; completedItems: number; estimatedTimeRemaining: number; startTime: number; activePhase: string; } class GlobalProgressTracker { private state: GlobalProgressState; private updateInterval: NodeJS.Timeout | null = null; constructor(totalItems: number) { this.state = { threads: new Map(), overallProgress: 0, totalItems, completedItems: 0, estimatedTimeRemaining: 0, startTime: Date.now(), activePhase: 'Initializing' }; this.startProgressUpdates(); } /** * Register a thread for progress tracking */ registerThread(threadId: string, totalItems: number, subTasks: string[]): void { this.state.threads.set(threadId, { threadId, status: 'waiting', totalItems, completedItems: 0, progress: 0, subTasks: new Map(subTasks.map(task => [task, { completed: 0, total: 0, progress: 0 }])), startTime: null, estimatedCompletion: null }); } /** * Update thread status */ updateThreadStatus(threadId: string, status: ThreadStatus): void { const thread = this.state.threads.get(threadId); if (thread) { thread.status = status; if (status === 'running' && !thread.startTime) { thread.startTime = Date.now(); } this.renderGlobalProgress(); } } /** * Update sub-task progress within a thread */ updateSubProgress(subTaskId: string, completed: number, total: number): void { for (const [threadId, thread] of this.state.threads) { if (thread.subTasks.has(subTaskId)) { const subTask = thread.subTasks.get(subTaskId)!; subTask.completed = completed; subTask.total = total; subTask.progress = total > 0 ? (completed / total) * 100 : 0; // Update thread overall progress this.calculateThreadProgress(thread); break; } } } /** * Calculate overall thread progress based on sub-tasks */ private calculateThreadProgress(thread: ThreadProgress): void { const subTaskProgresses = Array.from(thread.subTasks.values()); const averageProgress = subTaskProgresses.reduce((sum, task) => sum + task.progress, 0) / subTaskProgresses.length; thread.progress = averageProgress; thread.completedItems = Math.floor((averageProgress / 100) * thread.totalItems); // Calculate estimated completion if (thread.startTime && thread.progress > 0) { const elapsed = Date.now() - thread.startTime; const rate = thread.progress / elapsed; const remaining = (100 - thread.progress) / rate; thread.estimatedCompletion = Date.now() + remaining; } } /** * Start real-time progress updates */ private startProgressUpdates(): void { this.updateInterval = setInterval(() => { this.renderGlobalProgress(); }, 2000); // Update every 2 seconds } /** * Render the global progress dashboard */ private renderGlobalProgress(): void { // Clear console and render updated progress console.clear(); console.log(`\nπŸš€ AGILITY SYNC UPLOAD ORCHESTRATOR`); console.log(`πŸ“Š Total Entities: ${this.state.totalItems.toLocaleString()} | Phase: ${this.state.activePhase}`); // Phase 1: Independent Entities const thread1 = this.state.threads.get('independent-entities'); if (thread1) { console.log(`\nβ”Œβ”€ PHASE 1: INDEPENDENT ENTITIES ─────────────────────────┐`); this.renderThreadProgress(thread1); console.log(`β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜`); } // Phase 2: Batched Content const thread2 = this.state.threads.get('batched-content'); if (thread2) { console.log(`\nβ”Œβ”€ PHASE 2: DEPENDENCY-LEVEL CONTENT ────────────────────┐`); this.renderThreadProgress(thread2); console.log(`β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜`); } // Phase 3: Complex Entities const thread3 = this.state.threads.get('complex-entities'); if (thread3) { console.log(`\nβ”Œβ”€ PHASE 3: COMPLEX ENTITY CHAINS ───────────────────────┐`); this.renderThreadProgress(thread3); console.log(`β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜`); } // Overall summary const overallPercent = this.calculateOverallProgress(); const timeRemaining = this.calculateTimeRemaining(); console.log(`\nπŸ“ˆ Overall Progress: ${this.createProgressBar(overallPercent, 50)} ${overallPercent.toFixed(1)}%`); console.log(`⏱️ Estimated Time Remaining: ${timeRemaining}`); console.log(`🧡 Active Threads: ${this.getActiveThreadCount()}`); } private renderThreadProgress(thread: ThreadProgress): void { const statusIcon = this.getStatusIcon(thread.status); const progressBar = this.createProgressBar(thread.progress, 40); console.log(`β”‚ ${statusIcon} Thread: ${thread.threadId.toUpperCase().replace('-', ' ')}`); console.log(`β”‚ ${progressBar} ${thread.progress.toFixed(1)}%`); // Show sub-task details for (const [taskName, task] of thread.subTasks) { if (task.total > 0) { const taskBar = this.createProgressBar(task.progress, 20); const emoji = this.getTaskEmoji(taskName); console.log(`β”‚ ${emoji} ${taskName}: ${taskBar} ${task.completed}/${task.total}`); } } } private getStatusIcon(status: ThreadStatus): string { switch (status) { case 'waiting': return '⏸️'; case 'running': return '⏳'; case 'complete': return 'βœ…'; case 'error': return '❌'; default: return '❓'; } } private getTaskEmoji(taskName: string): string { const emojiMap: Record<string, string> = { 'models': 'πŸ“‹', 'templates': 'πŸ—οΈ', 'containers': 'πŸ“¦', 'assets': 'πŸ“Ž', 'content': 'πŸ“', 'pages': 'πŸ“„', 'galleries': 'πŸ–ΌοΈ' }; return emojiMap[taskName] || 'πŸ”§'; } private createProgressBar(percent: number, width: number): string { const filled = Math.floor((percent / 100) * width); const empty = width - filled; return 'β–ˆ'.repeat(filled) + 'β–‘'.repeat(empty); } private calculateOverallProgress(): number { const threadProgresses = Array.from(this.state.threads.values()); if (threadProgresses.length === 0) return 0; const weightedSum = threadProgresses.reduce((sum, thread) => { const weight = thread.totalItems / this.state.totalItems; return sum + (thread.progress * weight); }, 0); return weightedSum; } private calculateTimeRemaining(): string { const overallProgress = this.calculateOverallProgress(); if (overallProgress === 0) return 'Calculating...'; const elapsed = Date.now() - this.state.startTime; const rate = overallProgress / elapsed; const remainingMs = (100 - overallProgress) / rate; return this.formatTime(remainingMs); } private formatTime(ms: number): string { const seconds = Math.floor(ms / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); if (hours > 0) { return `${hours}h ${minutes % 60}m`; } else if (minutes > 0) { return `${minutes}m ${seconds % 60}s`; } else { return `${seconds}s`; } } private getActiveThreadCount(): number { return Array.from(this.state.threads.values()).filter(t => t.status === 'running').length; } /** * Clean up progress tracking */ cleanup(): void { if (this.updateInterval) { clearInterval(this.updateInterval); this.updateInterval = null; } } } ``` --- ## πŸ”„ Dependency Gate System ### **Thread Synchronization Mechanism** ```typescript interface DependencyGate { gateName: string; isOpen: boolean; waitingThreads: string[]; openedAt?: number; requiredBy: string[]; } class DependencyGateManager { private gates: Map<string, DependencyGate> = new Map(); private gateWaiters: Map<string, { resolve: Function; reject: Function }[]> = new Map(); constructor() { this.initializeGates(); } /** * Initialize all dependency gates */ private initializeGates(): void { const gateDefinitions = [ { name: 'models-complete', requiredBy: ['batched-content', 'complex-entities'] }, { name: 'templates-complete', requiredBy: ['batched-content', 'complex-entities'] }, { name: 'containers-complete', requiredBy: ['batched-content'] }, { name: 'independent-assets-complete', requiredBy: ['complex-entities'] }, { name: 'content-complete', requiredBy: ['complex-entities'] }, { name: 'all-entities-complete', requiredBy: [] } ]; gateDefinitions.forEach(gate => { this.gates.set(gate.name, { gateName: gate.name, isOpen: false, waitingThreads: [], requiredBy: gate.requiredBy }); this.gateWaiters.set(gate.name, []); }); } /** * Wait for a specific gate to open */ async waitForGate(gateName: string, threadId: string): Promise<void> { const gate = this.gates.get(gateName); if (!gate) { throw new Error(`Unknown gate: ${gateName}`); } if (gate.isOpen) { return; // Gate already open } console.log(` ⏳ Thread ${threadId} waiting for gate: ${gateName}`); gate.waitingThreads.push(threadId); return new Promise((resolve, reject) => { const waiters = this.gateWaiters.get(gateName)!; waiters.push({ resolve, reject }); }); } /** * Signal that a gate should open */ async signalGate(gateName: string, signallingThread: string): Promise<void> { const gate = this.gates.get(gateName); if (!gate) { throw new Error(`Unknown gate: ${gateName}`); } if (gate.isOpen) { return; // Already open } gate.isOpen = true; gate.openedAt = Date.now(); console.log(` πŸšͺ Gate opened: ${gateName} (by ${signallingThread})`); // Notify all waiting threads const waiters = this.gateWaiters.get(gateName)!; waiters.forEach(waiter => waiter.resolve()); this.gateWaiters.set(gateName, []); // Clear waiters // Log which threads can now proceed if (gate.waitingThreads.length > 0) { console.log(` βœ… Releasing threads: ${gate.waitingThreads.join(', ')}`); gate.waitingThreads = []; } } /** * Get gate status for debugging */ getGateStatus(): GateStatus[] { return Array.from(this.gates.values()).map(gate => ({ name: gate.gateName, isOpen: gate.isOpen, waitingThreadCount: gate.waitingThreads.length, openedAt: gate.openedAt, requiredBy: gate.requiredBy })); } } ``` --- ## πŸ”§ TypeScript Type Definitions ```typescript // Core types for parallel execution system type ThreadStatus = 'waiting' | 'running' | 'complete' | 'error'; interface ThreadProgress { threadId: string; status: ThreadStatus; totalItems: number; completedItems: number; progress: number; // 0-100 subTasks: Map<string, SubTaskProgress>; startTime: number | null; estimatedCompletion: number | null; } interface SubTaskProgress { completed: number; total: number; progress: number; // 0-100 } interface ThreadResult { threadId: string; successful: UploadResult[]; failed: UploadResult[]; totalTime: number; subResults: Map<string, SubThreadResult>; } interface SubThreadResult { uploaded: UploadResult[]; failed: UploadResult[]; totalTime: number; } interface UploadResult { sourceId: number; targetId?: number; error?: string; } interface GateStatus { name: string; isOpen: boolean; waitingThreadCount: number; openedAt?: number; requiredBy: string[]; } interface PageChain { chainId: string; pages: PageItem[]; dependencies: string[]; } abstract class UploadThread { protected config: ThreadConfiguration; protected progressTracker: GlobalProgressTracker; protected idMappingService: IdMappingService; protected gateManager: DependencyGateManager; constructor(config: ThreadConfiguration) { this.config = config; } abstract execute(sourceEntities: SourceEntities): Promise<ThreadResult>; protected async waitForGate(gateName: string): Promise<void> { return this.gateManager.waitForGate(gateName, this.config.threadId); } protected async signalGate(gateName: string): Promise<void> { return this.gateManager.signalGate(gateName, this.config.threadId); } } ``` --- ## πŸ§ͺ Execution Validation & Testing ### **Parallel Execution Tests** ```typescript describe('ParallelExecutionManager', () => { it('should execute threads in correct dependency order', async () => { // Test that thread 1 completes before thread 2 starts // Test that thread 2 completes before thread 3 starts }); it('should handle thread failures gracefully', async () => { // Test error isolation and recovery }); it('should coordinate dependency gates correctly', async () => { // Test gate signaling and waiting mechanisms }); it('should track progress accurately across all threads', async () => { // Test progress calculations and reporting }); }); describe('DependencyGateManager', () => { it('should block threads until dependencies are met', async () => { // Test gate waiting behavior }); it('should release all waiting threads when gate opens', async () => { // Test gate signaling behavior }); }); ``` --- **Status**: βœ… Parallel Execution Plan Complete **Next**: Task 19.1.4 - Design real-time ID mapping system for chain dependencies