Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,4 @@ jobs:

- name: Build all examples
shell: bash
run: |
set -e
for lpi in examples/*/*.lpi; do
echo "==> Building $lpi"
lazbuild "$lpi"
done
run: sh ./build-examples.sh Release
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ tests/TestRunner
tests/TestRunner.exe
tests/lib/

# Consolidated example binary outputs
/example-bin/

# Debug files
*.dbg
*.dcu
Expand Down Expand Up @@ -58,4 +61,4 @@ $RECYCLE.BIN/
## Linux
*~
.directory
.Trash-*
.Trash-*
58 changes: 58 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,64 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [0.9.0] - 2026-07-16

### Added

- `ThreadPool.Tasks`, a shared task coordination unit for both pool
implementations
- `Submit` overloads returning `IThreadPoolTask` handles with pending, running,
completed, failed, and cancelled states
- Timeout-aware `TrySubmit` overloads matching all four existing callback forms
- Individual task waiting, failure messages, terminal-state observation, and
race-safe pending cancellation
- `EThreadPoolDeadlock` protection when a callback or error handler attempts to
wait for its own task
- `IThreadPoolTaskBatch` for snapshot-based waits, status counts, indexed task
access, and batch pending cancellation
- Chunked `SubmitRange` overloads for indexed procedures and methods, with
inclusive bounds, automatic or explicit chunk sizes, and a returned batch
- v0.8 interface compile sentinel plus task, batch, cancellation-race, bounded
admission, lifetime, and range regression tests
- `TaskCoordination` API tour, plus production-shaped coordinated file-backup
and parallel log-analysis examples
- Root-level PowerShell and POSIX scripts that build every example into
`example-bin/`
- Complete task API documentation
- Separate benchmark cases for legacy queueing, tracked submission, individual
indexed submission, and chunked ranges

### Changed

- Simple pool queue storage now uses `IWorkItem` consistently, allowing shared
tracked work items while preserving its dynamically growing O(1) FIFO
- Both workers execute tracked work through one shared error/state transition
boundary
- Package version advanced to 0.9.0 and includes `ThreadPool.Tasks`

### Performance

- Legacy `Queue` retains its untracked path, so programs that do not request
task handles do not allocate task state or completion events
- Task completion events are created lazily only when unfinished work is waited
on
- Automatic ranges create at most `ThreadCount * 4` queue entries instead of
one entry per index
- Windows/FPC 3.2.2 release checks kept the legacy 20,000-task median within the
v0.8.5 10% regression budget; a 200,000-index Simple range was over 60x faster
than individual tracked submissions in the orientation run

### Compatibility

- The v0.8 `IThreadPool` interface and GUID are unchanged; new interface-based
callers use the separate `IThreadPoolTaskSource` capability
- Existing `Queue`, `TryQueue`, `WaitForAll`, lifecycle, error, constructor, and
`GlobalThreadPool` contracts remain available
- Cancellation applies only to pending callbacks. It never interrupts running
code, and a bounded-queue tombstone may occupy its slot until dequeued
- Bounded `SubmitRange` calls from the same pool's worker are rejected with
`EThreadPoolDeadlock` to prevent queue-starvation deadlocks

## [0.8.5] - 2026-07-14

### Added
Expand Down
70 changes: 63 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

# ThreadPool for Free Pascal

[![Version](https://img.shields.io/badge/version-0.8.5-8B5CF6.svg)](CHANGELOG.md)
[![Version](https://img.shields.io/badge/version-0.9.0-8B5CF6.svg)](CHANGELOG.md)
[![License: MIT](https://img.shields.io/badge/License-MIT-1E3A8A.svg)](LICENSE.md)
[![Free Pascal](https://img.shields.io/badge/Free%20Pascal-3.2.2+-3B82F6.svg)](https://www.freepascal.org/)
[![Lazarus](https://img.shields.io/badge/Lazarus-4.0+-60A5FA.svg)](https://www.lazarus-ide.org/)
Expand All @@ -19,11 +19,12 @@ producer-consumer workloads that need backpressure.

[Quick start](#quick-start) · [Cheat sheet](docs/CHEATSHEET.md) ·
[API documentation](#documentation) · [Examples](examples/) ·
[v0.8.5 release notes](docs/release-notes-v0.8.5.md)
[v0.9.0 release notes](docs/release-notes-v0.9.0.md)

> [!TIP]
> ✨ **New in v0.8.5:** refreshed project identity, a shorter README, and a new
> [cheat sheet](docs/CHEATSHEET.md). Runtime behavior is unchanged from v0.8.0.
> ✨ **New in v0.9.0:** observable task handles, task batches, efficient
> chunked ranges, and pending-work cancellation. Existing v0.8 queueing code
> remains source-compatible. See the [task API](docs/ThreadPool.Tasks-API.md).

> [!NOTE]
> This library is designed for simple parallel processing and learning-friendly
Expand All @@ -47,6 +48,9 @@ Both implementations provide:
- event-driven workers with no polling sleeps;
- four task forms: procedures, methods, and indexed variants;
- timeout-aware `TryQueue` and `WaitForAll` overloads;
- observable `Submit`/`TrySubmit` task handles;
- task batches and chunked `SubmitRange` processing;
- race-safe cancellation of work that has not started;
- deterministic, draining `Shutdown`;
- captured worker exceptions through `LastError`, `Errors`, and `OnError`; and
- automatic worker-count selection with safety limits.
Expand Down Expand Up @@ -130,6 +134,33 @@ end.
The first constructor argument is the worker count; `0` selects
`TThread.ProcessorCount`. The second is queue capacity.

### Tasks, batches, and ranges

Add `ThreadPool.Tasks` when work needs to be observed or coordinated:

```pascal
uses
ThreadPool.Tasks, ThreadPool.Simple;

var
Task: IThreadPoolTask;
Batch: IThreadPoolTaskBatch;
begin
Task := GlobalThreadPool.Submit(@DoWork);
if Task.WaitFor(250) and (Task.State = ttsFailed) then
WriteLn(Task.ErrorMessage);

Batch := GlobalThreadPool.SubmitRange(@ProcessItem, 0, 999);
Batch.WaitFor;
end;
```

`Task.Cancel` succeeds only while the task is pending. It never interrupts a
running callback. A range uses a small number of chunks by default instead of
creating one queue item per index. See the
[task API](docs/ThreadPool.Tasks-API.md) for batch counts, timeouts, explicit
chunk sizes, and bounded-pool rules.

## Lifecycle and timeouts

Both pools follow one monotonic lifecycle:
Expand Down Expand Up @@ -173,6 +204,9 @@ expires.
> also have no automatic execution deadline; add cancellation or
> application-level timeouts where needed.

Task handles and batches do not retain their pool. They may be kept after a
pool is freed, because pool destruction drains accepted work first.

## Error handling

Task exceptions are caught so a worker failure does not terminate the pool.
Expand Down Expand Up @@ -226,12 +260,30 @@ Requirements:

## Examples

Build every example in Release mode from the repository root:

```powershell
.\build-examples.ps1
```

```sh
sh ./build-examples.sh
```

Both scripts discover `examples/*/*.lpi` automatically and place the
executables in the ignored root-level `example-bin/` directory. Pass `Default`
to build the default mode, or use `-Rebuild` in PowerShell / `--rebuild` in the
shell script to force a complete rebuild.

| Start with | Demonstrates |
| --- | --- |
| [`Starter`](examples/Starter/) | Smallest compilable program with explanatory comments |
| [`SimpleDemo`](examples/SimpleDemo/) | Procedures, methods, indexes, and the global pool |
| [`ProdConSimpleDemo`](examples/ProdConSimpleDemo/) | Basic bounded-pool ownership and queueing |
| [`SimpleErrorHandlingBasic`](examples/SimpleErrorHandlingBasic/) | Reading captured errors after completion |
| [`TaskCoordination`](examples/TaskCoordination/) | Task handles, batches, ranges, and cancellation |
| [`CoordinatedFileBackup`](examples/CoordinatedFileBackup/) | Per-file progress, critical-failure policy, and pending cancellation |
| [`ParallelLogAnalyzer`](examples/ParallelLogAnalyzer/) | Chunked analysis followed by a parallel reporting phase |

More focused samples cover:

Expand All @@ -241,8 +293,11 @@ More focused samples cover:
[`SimpleWordCounter`](examples/SimpleWordCounter/), and
[`ProdConMessageProcessor`](examples/ProdConMessageProcessor/);
- advanced callbacks: [`SimpleErrorHandling`](examples/SimpleErrorHandling/);
- real I/O: [`ParallelFileHasher`](examples/ParallelFileHasher/) and
[`ParallelUrlFetcher`](examples/ParallelUrlFetcher/).
- real I/O: [`ParallelFileHasher`](examples/ParallelFileHasher/),
[`ParallelUrlFetcher`](examples/ParallelUrlFetcher/), and
[`CoordinatedFileBackup`](examples/CoordinatedFileBackup/);
- coordinated data processing:
[`ParallelLogAnalyzer`](examples/ParallelLogAnalyzer/).

## Documentation

Expand All @@ -251,9 +306,10 @@ More focused samples cover:
| [Cheat sheet](docs/CHEATSHEET.md) | Calls and safety rules at a glance |
| [Simple API](docs/ThreadPool.Simple-API.md) | Complete unbounded-pool reference |
| [Producer-Consumer API](docs/ThreadPool.ProducerConsumer-API.md) | Complete bounded-pool reference |
| [Tasks API](docs/ThreadPool.Tasks-API.md) | Submit, wait, batch, range, and cancellation contracts |
| [Simple technical guide](docs/ThreadPool.Simple-Technical.md) | Internal design and synchronization |
| [Producer-Consumer technical guide](docs/ThreadPool.ProducerConsumer-Technical.md) | Queue and backpressure internals |
| [v0.8.5 release notes](docs/release-notes-v0.8.5.md) | Current release scope and compatibility |
| [v0.9.0 release notes](docs/release-notes-v0.9.0.md) | Current release scope and compatibility |
| [Changelog](CHANGELOG.md) | Full version history |

The banner's editable source is
Expand Down
16 changes: 14 additions & 2 deletions benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,23 @@ lazbuild --build-mode=Release benchmarks/ThreadPoolBenchmark.lpi
./benchmarks/ThreadPoolBenchmark
```

The same source compiles against v0.7.0 and v0.8.0. It reports:
The v0.9.0 benchmark reports:

- completion time for a 20,000-task burst through each pool;
- completion time for a 20,000-task legacy `Queue` burst through each pool;
- completion time for an equivalent tracked `Submit` burst;
- individual tracked submission versus chunked `SubmitRange` for 200,000
indexed calls;
- average queue-to-start latency after workers have been idle.

For comparisons with v0.7.0 and v0.8.x, use the legacy queue and idle fields;
the tracked and range APIs did not exist in those releases.

Run each version several times on the same otherwise-idle machine and compare
medians. Debug logging must be disabled in both versions so console I/O is not
included in the scheduler measurement.

Do not enforce absolute millisecond thresholds on shared CI runners. Compare
five-run medians on the same otherwise-idle machine. The v0.9.0 release budget
allows at most a 10% regression in the legacy queue medians and expects the
Simple chunked range case to be at least 5x faster than individual tracked
indexed submissions.
Loading
Loading