Skip to content

Releases: ikelaiah/threadpool-fp

ThreadPool for Free Pascal — v0.8.5

Choose a tag to compare

@ikelaiah ikelaiah released this 13 Jul 22:53
5610b21

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.svg is 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.md collects 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

Choose a tag to compare

@ikelaiah ikelaiah released this 13 Jul 12:50
726ff37

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) and TryQueue(..., TimeoutMS) make deadlines explicit.
  • Exceptions raised by OnError are 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:

  • 0 performs an immediate attempt/check;
  • finite values bound the call from entry;
  • THREADPOOL_INFINITE waits 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/WaitForAll programs need no source changes.
  • Code that submitted work after shutdown must now handle
    EThreadPoolShutdown; the old Simple pool silently discarded that work.
  • TBackpressureConfig remains available, but threshold and low/medium-delay
    fields no longer sleep. Prefer TryQueue(..., TimeoutMS) in new code.

See CHANGELOG.md for the complete version history.

ThreadPool for Free Pascal v0.7.0

Choose a tag to compare

@ikelaiah ikelaiah released this 11 Jul 05:07
b36cfa3

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 fires OnError outside 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. LastError and ClearLastError behave exactly as
    before; the new members are purely additive.
  • OnError handlers run on a worker thread — synchronize any access to shared or
    UI state.

Examples

  • SimpleErrorHandlingBasic — the easy way: inspect Errors, ErrorCount, and
    LastError after WaitForAll.
  • SimpleErrorHandling — advanced handling with an OnError callback.
  • 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

Choose a tag to compare

@ikelaiah ikelaiah released this 12 Jun 07:46
0f16029

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.ProducerConsumer

Windows does not need cthreads and is unaffected.

What changed

  • README — a prominent note at the top of Quick Start explaining the
    cthreads requirement, 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.md and ThreadPool.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 cthreads guard (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 has cthreads and is fine.
  • If you are new to the library on Linux/macOS, add the cthreads guard shown
    above as the first unit in your program.

Full changelog

See CHANGELOG.md for the complete version history.

ThreadPool-FP v0.6.0

Choose a tag to compare

@ikelaiah ikelaiah released this 11 Jun 03:17
7e6d4fe

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 main branch.

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 cthreads unit. On Unix/Linux, any program that creates threads must include cthreads as 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. TSimpleWorkerThread hardcoded the stdcall calling convention on its IWorkerThread methods (QueryInterface/_AddRef/_Release). FPC's IInterface only uses stdcall on Windows and cdecl elsewhere, so the implementations didn't match on Linux. Now guarded with {$IFDEF WINDOWS}.
  • Hardened worker lifetime. IWorkerThread is now explicitly non-ref-counted (_AddRef/_Release return -1), so an interface
    assignment can never free a worker the pool still owns via ClearThreads. TSimpleThreadPool.Destroy is also now safe to run on a partially constructed pool.
  • Example projects couldn't find the library. Every example except Starter had the src search path only in its Release build mode, so lazbuild (which builds the Default mode) 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

Choose a tag to compare

@ikelaiah ikelaiah released this 14 Apr 09:03

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. Includes fpc/lazbuild one-liners and
    expected output directly in the header.
  • README: decision flowchart — a quick text tree to choose between
    ThreadPool.Simple and ThreadPool.ProducerConsumer.
  • README: Queue overload reference table — all 4 Queue signatures
    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 WaitForAll entirely
    • LastError only storing the last exception
    • Manually calling Free on GlobalThreadPool
  • README: compilation sanity-checkfpc and lazbuild one-liners with
    expected output, plus a reminder about {$mode objfpc}{$H+}.

Source readability

  • {$REGION} / {$ENDREGION} grouping added to ThreadPool.Simple.pas and
    ThreadPool.ProducerConsumer.pas — public API, internal worker thread, work
    item, and queue sections are now collapsible in Lazarus and VS Code.
  • Object lifetime WARNING comments added inline to SimpleDemo.lpr and
    ProdConSimpleDemo.lpr.

Documentation corrections

  • ThreadPool.Simple-API.md: full rewrite for clarity and structure; corrected
    LastError description — stores the raw exception message only, no thread-ID
    prefix.
  • ThreadPool.Simple-Technical.md: removed incorrect GlobalThreadPool.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 (WaitForAll was
    incorrectly inside the EQueueFullException handler); added missing WorkQueue
    property; fixed two examples that caught queue-full by message string equality —
    now catch EQueueFullException by type.
  • ThreadPool.ProducerConsumer-Technical.md: replaced simplified TryEnqueue
    snippet (missing retry loop) with the actual implementation; replaced
    pseudo-code Execute snippet with real code; added thread safety summary table.
  • Interface-Reference-Counting doc: updated test example to use LongTask
    instead of SlowTask, 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.