Releases: ikelaiah/threadpool-fp
Release list
ThreadPool for Free Pascal — v0.8.5
v0.8.5 is a documentation and project-identity release. Runtime behavior and
the public API are unchanged from v0.8.0.
Highlights
- A new banner gives the project a recognizable visual identity. The editable
threadpool-banner.svgis the source of the
synchronized 1800×600 PNG shown in the README. - The README now leads with pool selection and working examples, while detailed
API, implementation, and troubleshooting material stays in focused documents. - The new
CHEATSHEET.mdcollects the calls and safety rules
most users need during implementation. - Lazarus package metadata now reports version 0.8.5 and uses a shorter package
description.
Documentation layout
| Need | Start here |
|---|---|
| Choose a pool and run a first task | README |
| Recall a call or safety rule | Cheat sheet |
| Explore every public member | Simple API or Producer-Consumer API |
| Understand internals | Simple technical guide or Producer-Consumer technical guide |
| Upgrade from an earlier lifecycle model | v0.8.0 release notes |
Compatibility
No application changes are required when upgrading from v0.8.0. This release
does not change:
- queue ordering or capacity;
- worker-count selection;
- timeout units or meanings;
- shutdown and draining behavior;
- error capture and callback behavior; or
- any public type or method signature.
See the changelog for the complete version history.
ThreadPool for Free Pascal — v0.8.0
0.8.0 makes threadpool-fp event-driven, deterministic under shutdown and
failure, and measurably faster under burst and idle workloads.
Highlights
- Both pools now have the same monotonic lifecycle and draining
Shutdown. - Idle workers block on events instead of polling with
Sleep. - The Simple pool uses a dynamically growing O(1) circular FIFO.
- The bounded pool signals not-empty and not-full transitions and only waits
when its queue is actually full. WaitForAll(TimeoutMS)andTryQueue(..., TimeoutMS)make deadlines explicit.- Exceptions raised by
OnErrorare contained at the callback boundary. - Existing
Queue(...)source continues to compile unchanged. - Debug logging is off by default.
Lifecycle contract
Shutdown changes State from tpsAccepting to tpsDraining, prevents new
admission, waits for submissions already in progress, drains all accepted work,
wakes and joins workers, and publishes tpsStopped. Repeated calls are safe.
Queueing after shutdown raises EThreadPoolShutdown.
Task and OnError exceptions are contained, but callback execution is
synchronous and has no automatic deadline. A blocking callback can therefore
delay completion and Shutdown; operations that may block should implement an
application-level timeout or cancellation mechanism.
Timeout contract
Timeout values are milliseconds:
0performs an immediate attempt/check;- finite values bound the call from entry;
THREADPOOL_INFINITEwaits without a deadline.
The Simple pool is unbounded, so TryQueue normally returns immediately. The
Producer-Consumer pool returns False when no slot becomes available before
the deadline.
Verification
The v0.8.0 suite contains 58 tests covering both legacy behaviour and new
lifecycle, timeout, callback-failure, queue saturation, concurrent
admission/shutdown, and invalid-construction cases. On the development machine
it completes in roughly four seconds with zero heap leaks reported by heaptrc.
The benchmark in benchmarks/ was compiled unchanged against
v0.7.0 and v0.8.0 with FPC 3.2.2 -O3. Debug logging was disabled in both.
Median of three Windows runs:
| Workload | v0.7.0 | v0.8.0 | Change |
|---|---|---|---|
| Simple, 20,000-task burst | 234 ms | 109 ms | 2.1× faster |
| Producer, 20,000-task burst | 3,953 ms | 172 ms | 23.0× faster |
| Simple idle queue-to-start | 7.9 ms | <1 ms | timer-resolution floor |
| Producer idle queue-to-start | 81.3 ms | <1 ms | timer-resolution floor |
Upgrade notes
- Ordinary
Queue/WaitForAllprograms need no source changes. - Code that submitted work after shutdown must now handle
EThreadPoolShutdown; the old Simple pool silently discarded that work. TBackpressureConfigremains available, but threshold and low/medium-delay
fields no longer sleep. PreferTryQueue(..., TimeoutMS)in new code.
See CHANGELOG.md for the complete version history.
ThreadPool for Free Pascal v0.7.0
This release adds richer error handling. Both thread pools can now report
every task that failed — not just the most recent one — and notify you the
moment a task raises. Existing code keeps working unchanged: LastError behaves
exactly as before.
What's new
Collect every task error
Previously, LastError held only the most recent failure — if several tasks
failed, earlier errors were lost. The new Errors collection keeps them all
(oldest first):
var
Msg: string;
begin
Pool.ClearErrors;
for i := 0 to 9 do
Pool.Queue(@RiskyProc, i);
Pool.WaitForAll;
WriteLn(Pool.ErrorCount, ' task(s) failed:');
for Msg in Pool.Errors do
WriteLn(' - ', Msg);
Pool.ClearErrors; // resets the collection and LastError
end;The collection is capped at MAX_STORED_ERRORS (1000); beyond that the oldest
messages are dropped, so a flood of failing tasks can't exhaust memory.
React to failures as they happen
Assign an OnError callback to be notified immediately, instead of polling after
WaitForAll:
// Called from a worker thread — keep it short and thread-safe; synchronize if
// it touches the UI or shared state.
Pool.OnError := @MyHandler.OnTaskError;New API (both pools)
| Member | Description |
|---|---|
Errors: TStringArray |
All captured task error messages, oldest first |
ErrorCount: Integer |
Number of messages currently held |
OnError: TThreadPoolErrorEvent |
Fired (on a worker thread) per failed task |
ClearErrors |
Clears both Errors and LastError |
The API lives in TThreadPoolBase, so TSimpleThreadPool and
TProducerConsumerThreadPool both get it.
Under the hood
- Removed the now-redundant per-pool error lock; error capture is serialized by
the base class, which firesOnErroroutside its lock to avoid deadlocks. - Documentation corrected: task error messages are raw exception messages (an
earlier doc note incorrectly claimed they included thread IDs).
Upgrade notes
- No breaking changes.
LastErrorandClearLastErrorbehave exactly as
before; the new members are purely additive. OnErrorhandlers run on a worker thread — synchronize any access to shared or
UI state.
Examples
SimpleErrorHandlingBasic— the easy way: inspectErrors,ErrorCount, and
LastErrorafterWaitForAll.SimpleErrorHandling— advanced handling with anOnErrorcallback.ParallelFileHasher— hashes multiple files concurrently and reports real
file I/O failures.ParallelUrlFetcher— fetches multiple URLs concurrently and reports real
HTTP/DNS failures.
Verification
- Test suite: 43 tests, 0 errors, 0 failures, 0 unfreed memory blocks
(8 new tests cover the error-collection API on both pools). - Package and all 10 top-level examples build cleanly.
Full changelog
See CHANGELOG.md for the complete version history.
ThreadPool-FP v0.6.5
A small, documentation-focused release. There are no code or API changes —
existing programs compile and run exactly as before. The goal is to stop new
Linux/macOS users from hitting a confusing runtime crash.
Why this release exists
On Unix-like systems (Linux, macOS), Free Pascal does not install a
threading manager by default. Any program that creates threads must include the
cthreads unit as the first unit in its uses clause. Without it, creating
the thread pool fails at runtime with an access violation (exit code 217) —
not a compile error, so the build succeeds and the crash only appears when
the program runs.
This caught us during CI setup, and it will catch anyone building their own
program against this library. v0.6.5 documents the requirement everywhere a new
user is likely to look.
program MyApp;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}
cthreads, // MUST be first on Linux/macOS
{$ENDIF}
ThreadPool.Simple; // or ThreadPool.ProducerConsumerWindows does not need cthreads and is unaffected.
What changed
- README — a prominent note at the top of Quick Start explaining the
cthreadsrequirement, plus the{$IFDEF UNIX}cthreads{$ENDIF}guard added
to every Quick Start and Installation snippet, and a reminder in the
compilation Tip. - API docs — a platform note at the top of both
ThreadPool.Simple-API.mdandThreadPool.ProducerConsumer-API.md. - Roadmap — "Planned/In Progress" dropped adaptive thread adjustment (it
conflicts with the library's intentionally simple, fixed-count design) and
added richer error handling, planned for 0.7.0.
The examples in this repository already include the
cthreadsguard (added in
v0.6.0), so they build and run correctly on both platforms — see
examples/Starter/Starter.lpr.
Upgrade notes
- Nothing to change in your code. If your program already runs correctly on
Linux/macOS, it already hascthreadsand is fine. - If you are new to the library on Linux/macOS, add the
cthreadsguard shown
above as the first unit in your program.
Full changelog
See CHANGELOG.md for the complete version history.
ThreadPool-FP v0.6.0
This release focuses on developer experience and cross-platform reliability. There are no public API changes — existing code continues to compile and run
unchanged.
What's new
Continuous Integration
- GitHub Actions CI (
.github/workflows/ci.yml) — every push and pull request now builds the package, runs the full FPCUnit test suite, and builds all examples on both Linux and Windows. - Live CI badge — the README's hardcoded test-count badge has been replaced with a real build-status badge, so the README always reflects the current state of the
mainbranch.
Easier contributing
CONTRIBUTING.md— build and test instructions, code style guide (including the unit-filename casing rule), and the pull-request workflow.- Issue templates (
.github/ISSUE_TEMPLATE/) — structured forms for bug reports and feature requests, plus a link to Discussions for questions. - Pull request template (
.github/PULL_REQUEST_TEMPLATE.md) — a checklist covering tests, changelog, docs, and the unit-casing convention. - README: Contributing section linking to
CONTRIBUTING.md.
Cross-platform fix
Unit filenames now match their unit identifier casing exactly, so the library compiles on case-sensitive filesystems such as Linux. On Windows this was masked by the case-insensitive filesystem.
| Before | After |
|---|---|
src/threadpool.simple.pas |
src/ThreadPool.Simple.pas |
src/threadpool.producerconsumer.pas |
src/ThreadPool.ProducerConsumer.pas |
tests/threadpool.producerconsumer.tests.pas |
tests/ThreadPool.ProducerConsumer.Tests.pas |
The unit reference in tests/TestRunner.lpr was also normalized
(Threadpool. → ThreadPool.).
Bug fixes
Setting up CI surfaced several issues that previously only affected Linux (and example builds) — all were masked on Windows:
- Access violation on Linux at runtime (exit code 217). The test runner and example programs were missing the
cthreadsunit. On Unix/Linux, any program that creates threads must includecthreadsas its first unit; without it, constructing the thread pool crashed while setting up its worker threads and synchronization objects.{$IFDEF UNIX}cthreads{$ENDIF}was added to the test
runner and all 8 examples. Windows does not need it and is unaffected. - Compilation on non-Windows targets.
TSimpleWorkerThreadhardcoded thestdcallcalling convention on itsIWorkerThreadmethods (QueryInterface/_AddRef/_Release). FPC'sIInterfaceonly usesstdcallon Windows andcdeclelsewhere, so the implementations didn't match on Linux. Now guarded with{$IFDEF WINDOWS}. - Hardened worker lifetime.
IWorkerThreadis now explicitly non-ref-counted (_AddRef/_Releasereturn-1), so an interface
assignment can never free a worker the pool still owns viaClearThreads.TSimpleThreadPool.Destroyis also now safe to run on a partially constructed pool. - Example projects couldn't find the library. Every example except
Starterhad thesrcsearch path only in itsReleasebuild mode, solazbuild(which builds theDefaultmode) failed with "Can't find unit". The search path is now in each project's global compiler options. - Core-count-dependent tests. A few tests hardcoded thread-count expectations and queue-draining timing that only held on the developer's multi-core machine; on low-core CI runners the enforced 4-thread minimum made them fail. They now assert against the actual thread-count formula and overflow the queue with a burst sized off the real thread count, so they're deterministic on any number of cores.
Upgrade notes
- No code changes required.
uses ThreadPool.Simple;,uses ThreadPool.ProducerConsumer;, etc. are unchanged. - If you reference the source files by literal path (rare), update to the new capitalized filenames listed above.
Verification
CI builds the package, runs the test suite, and builds all 8 examples on both Linux and Windows:
- Test suite: 35 tests, 0 errors, 0 failures, 0 unfreed memory blocks.
- All 8 example projects build cleanly on both platforms.
Full changelog
See CHANGELOG.md for the complete version history.
ThreadPool-FP v0.5.0
This release focuses on making the library easier to pick up for new Free Pascal
developers, tightens up the documentation, and fixes three long-standing test
failures.
What's new
Easier onboarding
- New
examples/Starter/Starter.lpr— a single, heavily-commented file that
compiles and runs in under a minute. Includesfpc/lazbuildone-liners and
expected output directly in the header. - README: decision flowchart — a quick text tree to choose between
ThreadPool.SimpleandThreadPool.ProducerConsumer. - README: Queue overload reference table — all 4
Queuesignatures
side-by-side with use-cases and examples. - README: Common Mistakes section — annotated before/after code pairs for the
four most frequent pitfalls:- Freeing an object before
WaitForAll - Forgetting
WaitForAllentirely LastErroronly storing the last exception- Manually calling
FreeonGlobalThreadPool
- Freeing an object before
- README: compilation sanity-check —
fpcandlazbuildone-liners with
expected output, plus a reminder about{$mode objfpc}{$H+}.
Source readability
{$REGION}/{$ENDREGION}grouping added toThreadPool.Simple.pasand
ThreadPool.ProducerConsumer.pas— public API, internal worker thread, work
item, and queue sections are now collapsible in Lazarus and VS Code.- Object lifetime
WARNINGcomments added inline toSimpleDemo.lprand
ProdConSimpleDemo.lpr.
Documentation corrections
ThreadPool.Simple-API.md: full rewrite for clarity and structure; corrected
LastErrordescription — stores the raw exception message only, no thread-ID
prefix.ThreadPool.Simple-Technical.md: removed incorrectGlobalThreadPool.Free
from example; corrected "initialized on first use" (it is initialized at unit
startup); removed duplicate section heading; replaced bullet lists with tables.ThreadPool.ProducerConsumer-API.md: full rewrite for clarity; removed
duplicate Constructor section; fixed error handling example (WaitForAllwas
incorrectly inside theEQueueFullExceptionhandler); added missingWorkQueue
property; fixed two examples that caught queue-full by message string equality —
now catchEQueueFullExceptionby type.ThreadPool.ProducerConsumer-Technical.md: replaced simplifiedTryEnqueue
snippet (missing retry loop) with the actual implementation; replaced
pseudo-codeExecutesnippet with real code; added thread safety summary table.Interface-Reference-Countingdoc: updated test example to useLongTask
instead ofSlowTask, matching the fix to Test08.
Bug fixes (tests)
Three test failures were present since the tests were first written, they were
never passing, not regressions introduced in this release.
| Test | Root cause | Fix |
|---|---|---|
Test07_ParallelExecution |
IncrementCounter contained two LogTest (WriteLn) calls; 2000 serialised console writes across 1000 tasks consistently exceeded the timing assertion |
Removed LogTest calls from IncrementCounter |
Test08_QueueFullBehavior |
SlowTask (250 ms) was short enough for the single worker to drain the 2-slot queue before the 3rd enqueue, so EQueueFullException was never raised |
Replaced with LongTask (5 000 ms) to keep the queue full long enough |
Test12_LoadFactorCalculation |
LoadFactor is FCount / FCapacity — it drops to 0 the moment a worker calls TryDequeue, not when the task finishes. With only 2 tasks and 4+ workers, both items were dequeued before the assertion ran |
Increased queued task count to 50 so the queue backlog outlasts the dequeue race |
All 35 tests pass, 0 errors, 0 failures.
Full changelog
See CHANGELOG.md for the complete version history.