Skip to content

Latest commit

 

History

History
137 lines (95 loc) · 3.54 KB

File metadata and controls

137 lines (95 loc) · 3.54 KB

PowerQ Architecture

This document explains the internal design and architecture of PowerQ.
It is intended for developers who want to understand or contribute to the scheduler’s internals.


🏗 Core Concepts

PowerQ is a priority-based asynchronous task scheduler.
It manages tasks in two separate queues (priority and normal), enforces a concurrency limit, and ensures non-blocking execution using JavaScript’s event loop.


🔑 Key Components

1. Queues

  • Priority Queue (priorityQ)
    Stores tasks marked as high priority.
  • Normal Queue (normalQ)
    Stores tasks added by default (non-priority).

2. TaskRecord

Each task is stored as a TaskRecord object containing:

  • id: unique identifier
  • exec: the task function or promise
  • priority: boolean flag
  • addedAt: timestamp (used for custom logic like aging)
  • canceled: whether the task was canceled
  • controller: AbortController for cancellation
  • resolve / reject: hooks to settle the task’s result promise
  • state: "queued" | "running" | "completed"

3. Scheduler

The scheduler controls:

  • runningCount → how many tasks are currently executing
  • concurrency → maximum allowed concurrent tasks
  • queueSelector → custom logic to decide whether to pick from priorityQ or normalQ

⚙️ Execution Flow

flowchart TD
    A[Add Task] --> B{Priority?}
    B -- Yes --> P[Push to priorityQ]
    B -- No --> N[Push to normalQ]
    P --> C[Drain Scheduler]
    N --> C[Drain Scheduler]

    C --> D{runningCount < concurrency?}
    D -- No --> Stop[Wait]
    D -- Yes --> E[Pick Task]
    E -->|priorityQ first| Run[Execute Task]

    Run --> F[Promise Resolves/Rejects]
    F --> G[Resolve/Reject TaskHandle.result]
    G --> H[Decrement runningCount]
    H --> C
Loading

🌀 The _drain() Loop

  • Continuously checks if there are free concurrency slots.

  • Always picks from:

    1. queueSelector (if provided)
    2. Otherwise → priorityQ first, then normalQ
  • Runs the task via Promise.resolve().then(...).

  • On completion:

    • Resolves/rejects the promise
    • Removes task from tasks map
    • Decrements runningCount
    • Calls _drain() again to pick the next task

🛑 Cancellation

  • Each task has an AbortController.
  • Calling .cancel(taskId):
    • Removes the task from its queue if still queued
    • Marks as canceled
    • Calls AbortController.abort()
    • Rejects the promise with "Task canceled"

🔄 Promotion & Demotion

  • .promoteToPriority(taskId) Moves a queued task from normalQpriorityQ.

  • .demoteToNormal(taskId) Moves a queued task from priorityQnormalQ.


📊 Concurrency Control

  • Controlled via runningCount and concurrency.
  • User can change at runtime with .setConcurrency(n).
  • Scheduler automatically fills free slots immediately.

🌍 Cross-Platform Notes

  • Uses Promises and async/await → works in Node, browser, React Native.
  • Fallback AbortController implementation ensures compatibility even if not natively supported.
  • No dependencies.

📐 Design Principles

  • Non-blocking: No synchronous loops that block the event loop.
  • Lightweight: Zero dependencies, minimal memory footprint.
  • Extensible: User can plug in custom queueSelector strategies.
  • Robust: Tasks can be canceled, promoted, demoted at any time.

📦 Future Extensions

  • Multiple priority levels (beyond binary)
  • Task timeouts
  • Retry policies
  • Metrics (avg wait time, throughput)