or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

configuration-management.mdcontent-hashing.mdfile-operations.mdindex.mdpath-utilities.mdprocess-coordination.mdsystem-information.md
tile.json

process-coordination.mddocs/

Process Coordination

Service locking and process coordination utilities for managing concurrent Gatsby processes, preventing conflicts, and enabling inter-process communication during builds and development.

Capabilities

Service Locking

Creates lockfiles for coordinating between multiple Gatsby processes and preventing conflicts.

/**
 * Creates a lockfile for service coordination between Gatsby processes
 * @param programPath - Path to the Gatsby site directory
 * @param serviceName - Name of the service to lock (e.g., "build", "develop")
 * @param content - Data to store with the lock (process info, configuration, etc.)
 * @returns Promise resolving to unlock function or null if lock failed
 */
function createServiceLock(
  programPath: string,
  serviceName: string,
  content: Record<string, any>
): Promise<UnlockFn | null>;

/**
 * Function type for unlocking services
 */
type UnlockFn = () => Promise<void>;

Usage Examples:

import { createServiceLock } from "gatsby-core-utils";

// Lock build service to prevent concurrent builds
const unlockBuild = await createServiceLock(
  process.cwd(),
  "build",
  {
    pid: process.pid,
    startedAt: Date.now(),
    nodeVersion: process.version,
    command: process.argv.join(" ")
  }
);

if (unlockBuild) {
  try {
    console.log("Build lock acquired, starting build...");
    await runBuild();
  } finally {
    // Always unlock when done
    await unlockBuild();
    console.log("Build lock released");
  }
} else {
  console.error("Could not acquire build lock - another build may be running");
  process.exit(1);
}

// Lock development server
const unlockDevelop = await createServiceLock(
  "/path/to/gatsby-site",
  "develop",
  {
    pid: process.pid,
    port: 8000,
    host: "localhost",
    startedAt: Date.now()
  }
);

Service Information Retrieval

Retrieves information about locked services and their current state.

/**
 * Retrieves service data if the service is currently locked
 * @param programPath - Path to the Gatsby site directory
 * @param serviceName - Name of the service to query
 * @param ignoreLockfile - Whether to ignore lock status and return data anyway
 * @returns Promise resolving to service data or null if not locked/found
 */
function getService<T>(
  programPath: string,
  serviceName: string,
  ignoreLockfile?: boolean
): Promise<T | null>;

Usage Examples:

import { getService } from "gatsby-core-utils";

// Check if build is currently running
const buildService = await getService(process.cwd(), "build");
if (buildService) {
  console.log(`Build running since: ${new Date(buildService.startedAt)}`);
  console.log(`Build PID: ${buildService.pid}`);
  console.log(`Node version: ${buildService.nodeVersion}`);
} else {
  console.log("No build currently running");
}

// Get development server info
interface DevelopService {
  pid: number;
  port: number;
  host: string;
  startedAt: number;
}

const developService = await getService<DevelopService>(
  "/path/to/site",
  "develop"
);

if (developService) {
  console.log(`Dev server running on http://${developService.host}:${developService.port}`);
  console.log(`Started: ${new Date(developService.startedAt)}`);
}

// Force retrieve service data (ignore lock status)
const serviceData = await getService(process.cwd(), "build", true);

All Services Retrieval

Gets information about all services for a Gatsby site.

/**
 * Gets all services for a Gatsby site
 * @param programPath - Path to the Gatsby site directory
 * @returns Promise resolving to object containing all services data
 */
function getServices(programPath: string): Promise<any>;

Usage Examples:

import { getServices } from "gatsby-core-utils";

// Get overview of all running services
const services = await getServices(process.cwd());
console.log("Active services:", Object.keys(services));

// Display service status dashboard
for (const [serviceName, serviceData] of Object.entries(services)) {
  if (serviceData) {
    console.log(`${serviceName}: Running (PID ${serviceData.pid})`);
  }
}

// Check for conflicts before starting new service
const allServices = await getServices("/path/to/site");
const hasConflicts = Object.values(allServices).some(service => 
  service && service.port === desiredPort
);

if (hasConflicts) {
  console.error(`Port ${desiredPort} already in use by another service`);
}

Resource Locking

Generic resource locking for coordinating access to shared resources.

/**
 * Creates a lock for the specified resource
 * @param resources - Resource identifier to lock
 * @returns Promise resolving to unlock function
 */
function lock(resources: string): Promise<() => void>;

Usage Examples:

import { lock } from "gatsby-core-utils";

// Lock shared cache directory
const unlockCache = await lock("cache-directory");
try {
  // Perform cache operations that need exclusive access
  await cleanupCacheFiles();
  await rebuildCacheIndex();
} finally {
  unlockCache();
}

// Lock database or file resources
const unlockDatabase = await lock("site-database");
try {
  await performDatabaseMigration();
} finally {
  unlockDatabase();
}

// Multiple processes coordinating file access
const unlockConfigFile = await lock("gatsby-config");
try {
  const config = await readConfigFile();
  const updatedConfig = await processConfig(config);
  await writeConfigFile(updatedConfig);
} finally {
  unlockConfigFile();
}

Advanced Usage Patterns

Process Lifecycle Management

import { createServiceLock, getService } from "gatsby-core-utils";

class GatsbyBuildManager {
  private unlockFn: UnlockFn | null = null;
  
  async startBuild() {
    // Check if build already running
    const existingBuild = await getService(this.sitePath, "build");
    if (existingBuild) {
      throw new Error(`Build already running (PID: ${existingBuild.pid})`);
    }
    
    // Acquire lock
    this.unlockFn = await createServiceLock(this.sitePath, "build", {
      pid: process.pid,
      startedAt: Date.now(),
      buildId: this.generateBuildId(),
      environment: process.env.NODE_ENV
    });
    
    if (!this.unlockFn) {
      throw new Error("Failed to acquire build lock");
    }
    
    // Register cleanup handlers
    process.on('SIGINT', () => this.cleanup());
    process.on('SIGTERM', () => this.cleanup());
    process.on('uncaughtException', () => this.cleanup());
  }
  
  async cleanup() {
    if (this.unlockFn) {
      await this.unlockFn();
      this.unlockFn = null;
    }
  }
}

Service Discovery and Health Checking

import { getServices, getService } from "gatsby-core-utils";

async function checkServiceHealth(sitePath: string) {
  const services = await getServices(sitePath);
  const healthReport = {};
  
  for (const [serviceName, serviceData] of Object.entries(services)) {
    if (serviceData && serviceData.pid) {
      try {
        // Check if process is still running
        process.kill(serviceData.pid, 0);
        healthReport[serviceName] = {
          status: 'healthy',
          pid: serviceData.pid,
          uptime: Date.now() - serviceData.startedAt
        };
      } catch (error) {
        // Process not found - stale lock
        healthReport[serviceName] = {
          status: 'stale',
          pid: serviceData.pid,
          error: 'Process not found'
        };
      }
    }
  }
  
  return healthReport;
}

// Usage
const health = await checkServiceHealth(process.cwd());
console.log("Service health:", health);

Cross-Process Communication

import { createServiceLock, getService } from "gatsby-core-utils";

// Producer process
async function startDataProcessor() {
  const unlock = await createServiceLock(process.cwd(), "data-processor", {
    pid: process.pid,
    status: "initializing",
    progress: 0,
    lastUpdate: Date.now()
  });
  
  // Update progress periodically
  setInterval(async () => {
    await unlock(); // Release old lock
    const unlock2 = await createServiceLock(process.cwd(), "data-processor", {
      pid: process.pid,
      status: "processing", 
      progress: getCurrentProgress(),
      lastUpdate: Date.now()
    });
    unlock = unlock2;
  }, 5000);
}

// Consumer process
async function monitorDataProcessing() {
  const processorInfo = await getService(process.cwd(), "data-processor");
  if (processorInfo) {
    console.log(`Processing ${processorInfo.progress}% complete`);
    console.log(`Status: ${processorInfo.status}`);
    console.log(`Last update: ${new Date(processorInfo.lastUpdate)}`);
  }
}

Error Handling and Edge Cases

Lock Acquisition Failures

import { createServiceLock } from "gatsby-core-utils";

async function safeLockAcquisition(programPath: string, serviceName: string) {
  const maxRetries = 3;
  const retryDelay = 1000;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const unlock = await createServiceLock(programPath, serviceName, {
        pid: process.pid,
        attempt: attempt,
        timestamp: Date.now()
      });
      
      if (unlock) {
        return unlock;
      }
      
      console.log(`Lock acquisition failed (attempt ${attempt}/${maxRetries})`);
      
      if (attempt < maxRetries) {
        await new Promise(resolve => setTimeout(resolve, retryDelay * attempt));
      }
    } catch (error) {
      console.error(`Lock error on attempt ${attempt}:`, error.message);
    }
  }
  
  throw new Error(`Failed to acquire lock after ${maxRetries} attempts`);
}

Stale Lock Cleanup

import { getServices } from "gatsby-core-utils";

async function cleanupStaleLocks(programPath: string) {
  const services = await getServices(programPath);
  const staleServices = [];
  
  for (const [serviceName, serviceData] of Object.entries(services)) {
    if (serviceData && serviceData.pid) {
      try {
        // Test if process exists
        process.kill(serviceData.pid, 0);
      } catch (error) {
        // Process doesn't exist - stale lock
        staleServices.push(serviceName);
      }
    }
  }
  
  console.log(`Found ${staleServices.length} stale locks:`, staleServices);
  // Manual cleanup would involve removing lock files
}

Integration with Gatsby

Process coordination is critical for Gatsby's multi-process architecture:

  • Build coordination: Preventing multiple builds from running simultaneously
  • Development server: Managing dev server lifecycle and port allocation
  • Plugin processes: Coordinating plugin workers and background tasks
  • Cache management: Preventing concurrent cache operations and corruption
  • File watching: Coordinating file system watchers across processes

These utilities ensure that Gatsby's complex build system operates reliably even with multiple concurrent processes and shared resources.