diff --git a/CHANGELOG.md b/CHANGELOG.md
index 615def2..6a58c3d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,30 @@ 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.8.5] - 2026-07-14
+
+### Added
+
+- New project identity with an editable SVG banner source and a synchronized
+ 1800ร600 PNG used by GitHub-facing documentation
+- [`docs/CHEATSHEET.md`](docs/CHEATSHEET.md), a compact reference for pool
+ selection, queue overloads, timeouts, shutdown, errors, and object lifetime
+- Focused v0.8.5 release notes
+
+### Changed
+
+- Reorganized and shortened the README around choosing a pool, getting started,
+ lifecycle rules, and links to deeper documentation
+- Simplified the Lazarus package description and advanced its package version
+ to 0.8.5
+- Refined the banner for crisp queue and worker paths, restrained endpoint and
+ scheduler glow, and clearer subtitle spacing
+
+### Compatibility
+
+- Documentation and project identity release only; there are no runtime, API,
+ timeout, queueing, or lifecycle behavior changes from v0.8.0
+
## [0.8.0] - 2026-07-13
### Added
diff --git a/README.md b/README.md
index 488a3f1..95c7f99 100644
--- a/README.md
+++ b/README.md
@@ -1,683 +1,282 @@
-# ๐ ThreadPool for Free Pascal
+
+
+
-[](CHANGELOG.md)
-[](https://opensource.org/licenses/MIT)
+# ThreadPool for Free Pascal
+
+[](CHANGELOG.md)
+[](LICENSE.md)
[](https://www.freepascal.org/)
[](https://www.lazarus-ide.org/)
-
-
-
[](https://github.com/ikelaiah/threadpool-fp/actions/workflows/ci.yml)
-[](docs/)
-[](README.md)
-
-A lightweight, easy-to-use thread pool implementation for Free Pascal. Simplify parallel processing for simple tasks! โก
+
+
+
-> [!IMPORTANT]
->
-> Parallel processing can improve performance for CPU-intensive tasks that can be executed independently. However, not all tasks benefit from parallelization. See [Thread Management](#-thread-management) for important considerations.
+A lightweight, dependency-free thread pool library for Free Pascal. It provides
+an unbounded pool for straightforward parallel work and a bounded pool for
+producer-consumer workloads that need backpressure.
-> [!NOTE]
-> This library was originally written to explore the concept of thread pools in Free Pascal. It has since grown into a stable, tested implementation suitable for simple parallel processing tasks.
->
-> It is **not designed** for high-load or production-scale applications. For those use cases, see the alternatives below.
+[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)
> [!TIP]
->
-> If you are looking for performant and battle-tested threading libraries, please check out these alternatives:
->
-> - [Mormot2 Threading Library](https://github.com/synopse/mORMot2) by [@synopse](https://github.com/synopse)
-> - [ezthreads](https://github.com/mr-highball/ezthreads) by [@mr-highball](https://github.com/mr-highball)
-> - [OmniThreadLibrary](https://github.com/gabr42/OmniThreadLibrary) by [@gabr42](https://github.com/gabr42) (Delphi-only)
-> - Or use threading in other languages via DLL/EXE calls;
-> - Go lang with [Goroutines](https://go.dev/tour/concurrency/1)
-> - Python with [concurrent.futures](https://docs.python.org/3/library/concurrent.futures.html)
-> - Rust with [threadpool](https://github.com/lifthrasiir/threadpool)
-> - Any other language that supports modern threading
-
-
-## ๐ Table of Contents
-- [๐ ThreadPool for Free Pascal](#-threadpool-for-free-pascal)
- - [๐ Table of Contents](#-table-of-contents)
- - [โจ Features](#-features)
- - [ThreadPool Implementations](#threadpool-implementations)
- - [1. Simple Thread Pool (`ThreadPool.Simple`)](#1-simple-thread-pool-threadpoolsimple)
- - [2. Producer-Consumer Thread Pool (`ThreadPool.ProducerConsumer`)](#2-producer-consumer-thread-pool-threadpoolproducerconsumer)
- - [Shared Features](#shared-features)
- - [๐ Quick Start](#-quick-start)
- - [Simple Thread Pool](#simple-thread-pool)
- - [Producer-Consumer Thread Pool](#producer-consumer-thread-pool)
- - [Timeouts and shutdown (v0.8.0)](#timeouts-and-shutdown-v080)
- - [Error Handling Simple Thread Pool](#error-handling-simple-thread-pool)
- - [Error Handling Producer-Consumer Thread Pool](#error-handling-producer-consumer-thread-pool)
- - [Tips](#tips)
- - [๐ Examples](#-examples)
- - [Simple Thread Pool Examples](#simple-thread-pool-examples)
- - [Producer-Consumer Examples](#producer-consumer-examples)
- - [๐ ๏ธ Installation](#๏ธ-installation)
- - [โ๏ธ Requirements](#๏ธ-requirements)
- - [๐ Documentation](#-documentation)
- - [๐งช Testing](#-testing)
- - [๐งต Thread Management](#-thread-management)
- - [Thread Count Rules](#thread-count-rules)
- - [Implementation Characteristics](#implementation-characteristics)
- - [๐ง Planned/In Progress](#-plannedin-progress)
- - [๐ Acknowledgments](#-acknowledgments)
- - [๐ License](#-license)
- - [๐ Changelog](CHANGELOG.md)
-
-## โจ Features
-
-This library provides two thread pool implementations, each with its own strengths:
-
-### ThreadPool Implementations
-
-#### 1. Simple Thread Pool (`ThreadPool.Simple`)
-```pascal
-uses ThreadPool.Simple;
-```
-- Global singleton instance for quick use
-- Event-driven, dynamically growing FIFO work queue
-- Automatic thread count management
-- Best for simple parallel tasks
-- Lower memory overhead
-
-#### 2. Producer-Consumer Thread Pool (`ThreadPool.ProducerConsumer`)
-```pascal
-uses ThreadPool.ProducerConsumer;
-```
-
-A thread pool with a fixed-size circular buffer (1024 items) and event-driven
-backpressure:
-
-- **Queue Management**
- - Fixed-size circular buffer for predictable memory usage
- - Efficient space reuse without dynamic resizing
- - Configurable capacity (default: 1024 items)
-
-- **Backpressure Handling**
- - Producers wait only while the queue is actually full
- - `TryQueue(..., TimeoutMS)` reports saturation without raising
- - Existing `Queue(...)` calls retain a bounded default wait and raise
- `EQueueFullException` if that wait expires
-
-- **Monitoring & Debug**
- - Thread-safe error capture
- - Internal debug logging is disabled by default
-
-> [!WARNING]
->
-> For sustained saturation, prefer `TryQueue(..., TimeoutMS)` and handle a
-> `False` result explicitly. Legacy `Queue(...)` raises `EQueueFullException`
-> when its compatibility wait expires.
-
-### Shared Features
-
-- **Thread Count Management**
- - Minimum 4 threads for optimal parallelism
- - Maximum 2ร `ProcessorCount` to prevent overload
- - Fixed count after initialization
-
-- **Task Types Support**
- - Simple procedures: `Pool.Queue(@MyProc)`
- - Object methods: `Pool.Queue(@MyObject.MyMethod)`
- - Indexed variants: `Pool.Queue(@MyProc, Index)`
-
-- **Lifecycle & Thread Safety โ v0.8.0**
- - Built-in synchronization
- - Event-driven workers; no polling sleeps
- - `Shutdown` stops admission, drains accepted work, and joins workers
- - Queueing after shutdown raises `EThreadPoolShutdown`
- - `WaitForAll(TimeoutMS)` and `TryQueue(..., TimeoutMS)` use milliseconds;
- zero means do not wait and `THREADPOOL_INFINITE` means no timeout
-
-- **Error Management**
- - Worker exceptions are caught automatically; the pool keeps running
- - `LastError` for the most recent failure
- - `Errors` collection captures **all** failed-task messages (capped, oldest dropped) โ *v0.7.0*
- - Optional `OnError` callback fired per failed task โ *v0.7.0*
- - Exceptions raised by `OnError` are contained and cannot terminate workers
- - Callback execution remains synchronous; keep callbacks short and bounded
+> โจ **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.
> [!NOTE]
-> Thread count is determined by `TThread.ProcessorCount` at startup and remains fixed. See [Thread Management](#-thread-management) for details.
+> This library is designed for simple parallel processing and learning-friendly
+> integration. It is not intended to replace high-load, production-scale
+> frameworks such as [mORMot2](https://github.com/synopse/mORMot2),
+> [ezthreads](https://github.com/mr-highball/ezthreads), or
+> [OmniThreadLibrary](https://github.com/gabr42/OmniThreadLibrary).
-## ๐ Quick Start
+## Choose a pool
-> [!IMPORTANT]
-> **On Linux/macOS, your program must use the `cthreads` unit โ and it must be the *first* unit in your program's `uses` clause.**
->
-> Free Pascal does not install a threading manager by default on Unix-like systems. Without `cthreads`, creating the thread pool fails at runtime with an access violation (exit code 217). Windows does not need it.
->
-> ```pascal
-> program MyApp;
-> {$mode objfpc}{$H+}
-> uses
-> {$IFDEF UNIX}
-> cthreads, // MUST be first on Linux/macOS
-> {$ENDIF}
-> ThreadPool.Simple; // or ThreadPool.ProducerConsumer
-> ```
->
-> The examples in this repository already include this guard โ see `examples/Starter/Starter.lpr`.
->
-> From the [official FPC documentation](https://www.freepascal.org/docs-html/rtl/cthreads/index.html): *"The cthreads unit simply needs to be included in the uses clause of the program, preferably the very first unit, and the initialization section of the unit will do all the work."*
-
-### Simple Thread Pool
-```pascal
-uses
- {$IFDEF UNIX}cthreads,{$ENDIF} // see the note above โ required on Linux/macOS
- ThreadPool.Simple;
+| | `ThreadPool.Simple` | `ThreadPool.ProducerConsumer` |
+| --- | --- | --- |
+| Queue | Dynamically growing FIFO | Fixed-size circular FIFO |
+| Capacity | Unbounded | Configurable; 1024 by default |
+| Submission | Immediate | Timeout-aware backpressure |
+| Convenience | Managed `GlobalThreadPool` | Create a pool instance |
+| Best for | Predictable, moderate fire-and-forget work | Producers that may outpace consumers |
-// Simple parallel processing
-procedure ProcessItem(index: Integer);
-begin
- WriteLn('Processing item: ', index);
-end;
+Both implementations provide:
-begin
- // Queue multiple items
- for i := 1 to 5 do
- GlobalThreadPool.Queue(@ProcessItem, i);
-
- GlobalThreadPool.WaitForAll;
-end;
-```
+- event-driven workers with no polling sleeps;
+- four task forms: procedures, methods, and indexed variants;
+- timeout-aware `TryQueue` and `WaitForAll` overloads;
+- deterministic, draining `Shutdown`;
+- captured worker exceptions through `LastError`, `Errors`, and `OnError`; and
+- automatic worker-count selection with safety limits.
-### Producer-Consumer Thread Pool
-```pascal
-uses
- {$IFDEF UNIX}cthreads,{$ENDIF} // see the note above โ required on Linux/macOS
- ThreadPool.ProducerConsumer;
+## Quick start
-procedure DoWork;
-begin
- WriteLn('Working in thread: ', GetCurrentThreadId);
-end;
-
-var
- Pool: TProducerConsumerThreadPool;
-begin
- Pool := TProducerConsumerThreadPool.Create;
- try
- Pool.Queue(@DoWork);
- Pool.WaitForAll;
- finally
- Pool.Free;
- end;
-end;
-```
-
-### Timeouts and shutdown (v0.8.0)
-
-Both pools use the same lifecycle contract and timeout units:
-
-```pascal
-// Existing source remains valid and waits indefinitely for completion.
-Pool.Queue(@DoWork);
-Pool.WaitForAll;
-
-// Timeout-aware wait: False means work is still running after 250 ms.
-if not Pool.WaitForAll(250) then
- WriteLn('Still working');
-
-// ProducerConsumer is bounded; this waits at most 50 ms for queue space.
-if not Pool.TryQueue(@DoWork, 50) then
- WriteLn('Queue is saturated');
-
-// Stop accepting work, drain every accepted task, then join all workers.
-Pool.Shutdown;
-```
-
-Timeouts are milliseconds. `0` performs only an immediate check and
-`THREADPOOL_INFINITE` waits without a deadline. `TSimpleThreadPool` is
-unbounded, so its `TryQueue` timeout is accepted for API symmetry but queue
-capacity cannot time out. After `Shutdown`, all `Queue` and `TryQueue` overloads
-raise `EThreadPoolShutdown`.
-
-Exception containment does not impose an execution deadline. A task or
-`OnError` handler that blocks indefinitely continues to occupy its worker, and
-`Shutdown` waits because it drains all accepted work. Use application-level
-timeouts or cancellation inside operations that may block.
+> [!IMPORTANT]
+> ๐งต **Unix thread setup:** On Linux and macOS, `cthreads` must be the first unit
+> in the program's `uses` clause. Without it, Free Pascal can compile
+> successfully but fail at runtime when the pool creates worker threads. Windows
+> does not need `cthreads`.
-`WaitForAll` is not an admission barrier for unrelated producer threads. If
-producers may still submit concurrently, coordinate them first or call
-`Shutdown`, which closes admission before draining.
+### Simple pool
-### Error Handling Simple Thread Pool
+Use the managed global pool when you only need to submit work and wait for it:
```pascal
-program ErrorHandling;
+program SimplePoolDemo;
-{$mode objfpc}{$H+}{$J-}
+{$mode objfpc}{$H+}
uses
- Classes, SysUtils, ThreadPool.Simple;
+ {$IFDEF UNIX}
+ cthreads,
+ {$ENDIF}
+ ThreadPool.Simple;
-procedure RiskyProcedure;
+procedure ProcessItem(Index: Integer);
begin
- raise Exception.Create('Something went wrong!');
+ WriteLn('Processing item ', Index);
end;
var
- Pool: TSimpleThreadPool;
+ I: Integer;
begin
- Pool := TSimpleThreadPool.Create(4); // Create with 4 threads
- try
- Pool.Queue(@RiskyProcedure);
- Pool.WaitForAll;
-
- // Check for errors after completion
- if Pool.LastError <> '' then
- begin
- WriteLn('An error occurred: ', Pool.LastError);
- Pool.ClearLastError; // Clear for reuse if needed
- end;
- finally
- Pool.Free;
- end;
+ for I := 1 to 5 do
+ GlobalThreadPool.Queue(@ProcessItem, I);
+
+ GlobalThreadPool.WaitForAll;
end.
```
-### Error Handling Producer-Consumer Thread Pool
+`GlobalThreadPool` is created and destroyed by the unit. Do not free it
+yourself.
+
+### Producer-consumer pool
+
+Use a bounded pool when submission may need to wait for queue space:
```pascal
-program ErrorHandling;
+program BoundedPoolDemo;
-{$mode objfpc}{$H+}{$J-}
+{$mode objfpc}{$H+}
uses
- Classes, SysUtils, ThreadPool.ProducerConsumer;
+ {$IFDEF UNIX}
+ cthreads,
+ {$ENDIF}
+ ThreadPool.ProducerConsumer;
-procedure RiskyProcedure;
+procedure DoWork;
begin
- raise Exception.Create('Something went wrong!');
+ WriteLn('Working');
end;
var
Pool: TProducerConsumerThreadPool;
begin
- Pool := TProducerConsumerThreadPool.Create;
+ Pool := TProducerConsumerThreadPool.Create(0, 1024);
try
- try
- Pool.Queue(@RiskyProcedure);
- except
- on E: EQueueFullException do
- WriteLn('Queue is full after retries: ', E.Message);
- end;
-
- Pool.WaitForAll;
-
- // Check for errors after completion
- if Pool.LastError <> '' then
- begin
- WriteLn('An error occurred: ', Pool.LastError);
- Pool.ClearLastError; // Clear for reuse if needed
- end;
+ if not Pool.TryQueue(@DoWork, 50) then
+ WriteLn('Queue remained full for 50 ms');
+
+ Pool.Shutdown; // close admission, drain accepted work, join workers
finally
Pool.Free;
end;
end.
```
-### Capturing all task errors (v0.7.0)
-
-`LastError` only holds the **most recent** failure. To inspect **every** failed
-task, use the `Errors` collection (oldest first, capped at `MAX_STORED_ERRORS = 1000`).
-This works the same on both pools:
-
-```pascal
-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;
-```
-
-Prefer to react the moment a task fails (instead of polling after `WaitForAll`)?
-Assign an `OnError` callback:
-
-```pascal
-// IMPORTANT: OnError is called synchronously from a worker thread. Keep the
-// handler short, bounded, and thread-safe; synchronize if it touches the UI or
-// shared state.
-Pool.OnError := @MyHandler.OnTaskError;
-```
-
-### Tips
-
-> [!NOTE]
-> **Error Handling**
-> - ๐ก๏ธ Exceptions are caught and stored without terminating workers
-> - โก Pool continues operating after exceptions
-> - ๐ Use ClearLastError to reset error state
->
-> **Debugging**
-> - ๐ Task failures are collected without stopping workers
-> - ๐ Internal debug logging is disabled by default
-> - ๐ Queue capacity monitoring available
+The first constructor argument is the worker count; `0` selects
+`TThread.ProcessorCount`. The second is queue capacity.
+## Lifecycle and timeouts
-### Which implementation should I use?
+Both pools follow one monotonic lifecycle:
```text
-Need a thread pool?
-โโ Tasks are fire-and-forget, count is predictable, low overhead wanted?
-โ โโ โ Use ThreadPool.Simple (or the GlobalThreadPool singleton)
-โโ Producer can outpace consumers, or you need queue overflow control?
- โโ โ Use ThreadPool.ProducerConsumer
+tpsAccepting -> tpsDraining -> tpsStopped
```
-**Use Simple Thread Pool when:**
-- An unbounded, dynamically growing queue is appropriate
-- Task count is predictable and moderate
-- Low memory overhead is important
-- Global instance (GlobalThreadPool) convenience desired
-- Simple error handling is sufficient
-
-**Use Producer-Consumer Pool when:**
-- High volume of tasks with rate control needed
-- Backpressure handling required
-- Queue overflow protection important
-- Need detailed execution monitoring
-- Want bounded submission with explicit timeouts
-
-### Queue overload reference
-
-All four `Queue` overloads share the same pattern โ pick the one that fits your task:
-
-| Overload | Signature | Use when | Example |
-| --- | --- | --- | --- |
-| Plain procedure | `Queue(@MyProc)` | Standalone procedure, no shared state needed | File I/O, independent calculations |
-| Object method | `Queue(@MyObj.MyMethod)` | Task needs access to object fields/state | Counter objects, result accumulators |
-| Indexed procedure | `Queue(@MyProc, i)` | Loop parallelism over an array/range | `for i := 0 to N-1 do Queue(@Proc, i)` |
-| Indexed method | `Queue(@MyObj.MyMethod, i)` | Loop parallelism + object state | Parallel array transform on an object |
-
-> [!NOTE]
-> `LastError` holds only the **most recent** exception. To see **every** failed task, use the `Errors` collection (added in v0.7.0) โ see [Capturing all task errors](#capturing-all-task-errors-v070). Call `ClearErrors` before reusing the pool.
-
-
-## ๐ Examples
-
-### Getting Started
-
-1. ๐ **Starter** (`examples/Starter/Starter.lpr`)
- - The absolute minimum to compile and run
- - Heavily commented โ every line explained
- - Best first file to read before the other examples
-
-### Simple Thread Pool Examples
-
-1. ๐ **Simple Demo** (`examples/SimpleDemo/SimpleDemo.lpr`)
- - Basic usage with GlobalThreadPool
- - Demonstrates procedures and methods
- - Shows proper object lifetime
-
-2. ๐ข **Thread Pool Demo** (`examples/SimpleThreadpoolDemo/SimpleThreadpoolDemo.lpr`)
- - Custom thread pool management
- - Thread-safe operations
- - Error handling patterns
-
-3. ๐ **Word Counter** (`examples/SimpleWordCounter/SimpleWordCounter.lpr`)
- - Queue-based task processing
- - Thread-safe counters
- - File I/O with queue management
-
-4. ๐ข **Square Numbers** (`examples/SimpleSquareNumbers/SimpleSquareNumbers.lpr`)
- - High volume task processing
- - Queue full handling
- - Performance comparison
-
-5. ๐ก๏ธ **Error Handling โ Basic** (`examples/SimpleErrorHandlingBasic/SimpleErrorHandlingBasic.lpr`)
- - **Start here** for error handling โ the easy way
- - Just queue, `WaitForAll`, then read `Errors`/`ErrorCount`/`LastError`
- - No callback, no custom class, no locking required
-
-6. ๐ก๏ธ **Error Handling โ Advanced** (`examples/SimpleErrorHandling/SimpleErrorHandling.lpr`)
- - Adds the `OnError` callback to react *while* tasks run
- - Shows the thread-safe handler pattern (needed only because the handler keeps shared state)
-
-7. ๐ **Parallel File Hasher** (`examples/ParallelFileHasher/ParallelFileHasher.lpr`)
- - Hashes multiple files concurrently with `ThreadPool.Simple`
- - Demonstrates real file I/O failures captured through `Errors`/`OnError`
- - Creates and cleans up its own sample files
-
-### Producer-Consumer Examples
-
-1. ๐ **Simple Demo** (`examples/ProdConSimpleDemo/ProdConSimpleDemo.lpr`)
- - Basic usage with ProducerConsumerThreadPool
- - Demonstrates procedures and methods
- - Shows proper object lifetime
-
-2. ๐ข **Square Numbers** (`examples/ProdConSquareNumbers/ProdConSquareNumbers.lpr`)
- - High volume task processing
- - Queue full handling
- - Backpressure demonstration
- - Performance monitoring
-
-3. ๐ **Message Processor** (`examples/ProdConMessageProcessor/ProdConMessageProcessor.lpr`)
- - Queue-based task processing
- - Thread-safe message handling
- - Graceful shutdown
- - Error handling patterns
-
-4. ๐ **Parallel URL Fetcher** (`examples/ParallelUrlFetcher/ParallelUrlFetcher.lpr`)
- - Fetches multiple URLs concurrently with `ThreadPool.ProducerConsumer`
- - Demonstrates real HTTP/DNS failures captured through `Errors`/`OnError`
- - Uses one HTTP client per task so clients are not shared across threads
-
-
-## ๐ ๏ธ Installation
-
-1. Add the `src` directory to your project's search path
-2. Choose your implementation:
-
- For Simple Thread Pool:
- ```pascal
- uses
- {$IFDEF UNIX}cthreads,{$ENDIF} // required on Linux/macOS (must be first)
- ThreadPool.Simple;
- ```
-
- For Producer-Consumer Thread Pool:
- ```pascal
- uses
- {$IFDEF UNIX}cthreads,{$ENDIF} // required on Linux/macOS (must be first)
- ThreadPool.ProducerConsumer;
- ```
-
-3. Start using:
- - Simple: Use `GlobalThreadPool` or create `TSimpleThreadPool`
- - Producer-Consumer: Create `TProducerConsumerThreadPool`
-
-### Verify your setup
-
-Compile and run the simplest demo from the command line to confirm everything is wired up correctly:
-
-```bash
-# Using the Free Pascal compiler directly
-fpc -Fu./src examples/SimpleDemo/SimpleDemo.lpr && ./SimpleDemo
+`Shutdown` stops new admission, waits for submissions already entering the
+pool, drains every accepted task, wakes workers, and joins them. It is safe to
+call more than once. Queueing after shutdown begins raises
+`EThreadPoolShutdown`.
-# Or build with Lazarus from the command line
-lazbuild examples/SimpleDemo/SimpleDemo.lpi && ./SimpleDemo
-```
+```pascal
+Pool.WaitForAll; // wait indefinitely
-Expected output (order may vary โ tasks run in parallel):
+if not Pool.WaitForAll(250) then // milliseconds
+ WriteLn('Work remains');
-```text
-Demo of ThreadPool functionality:
---------------------------------
-1. Queueing simple procedure
-2. Queueing method of a class
-3. Queueing indexed procedure
-4. Queueing method with index of a class
---------------------------------
-Waiting for all tasks to complete...
-Simple procedure executed
-Method executed
-Indexed procedure executed with index: 1
-Method with index executed: 2
---------------------------------
-All tasks completed successfully!
+Pool.Shutdown;
```
-> [!TIP]
-> Make sure your source file starts with `{$mode objfpc}{$H+}`. Without this, Free Pascal defaults to TP/Delphi-7 mode and some syntax will not compile.
->
-> On Linux/macOS, also ensure `{$IFDEF UNIX}cthreads{$ENDIF}` is the **first** unit in your program's `uses` clause (see the [Quick Start](#-quick-start) note). Forgetting it causes a runtime access violation, not a compile error โ so the build succeeds but the program crashes when it creates the pool.
-
-## โ๏ธ Requirements
-
-- ๐ป Free Pascal 3.2.2 or later
-- ๐ฆ Lazarus 4.0 or later
-- ๐ No external dependencies
-
-## ๐ Documentation
-
-- [ThreadPool.Simple API Documentation](docs/ThreadPool.Simple-API.md)
-- [ThreadPool.Simple Technical Details](docs/ThreadPool.Simple-Technical.md)
-- [ThreadPool.ProducerConsumer API Documentation](docs/ThreadPool.ProducerConsumer-API.md)
-- [ThreadPool.ProducerConsumer Technical Details](docs/ThreadPool.ProducerConsumer-Technical.md)
-- [v0.8.0 Release Notes](docs/release-notes-v0.8.0.md)
-
-## ๐งช Testing
-
-1. Go to the `tests/` directory
-2. Open `TestRunner.lpi` in Lazarus IDE and compile
-3. Run `./TestRunner.exe -a -p --format=plain` to see the test results.
-4. Ensure all tests pass to verify the library's functionality
-
-The complete suite normally finishes in a few seconds. Release benchmarks live
-in [`benchmarks/`](benchmarks/).
-
-## ๐งต Thread Management
-
-### Thread Count Rules
-- Default: Uses ProcessorCount when thread count โค 0
-- Minimum: 4 threads enforced
-- Maximum: 2ร ProcessorCount
-- Fixed after creation (no dynamic scaling)
-
-### Implementation Characteristics
+Timeout values use these rules:
-**Simple Thread Pool**
-- Dynamically growing O(1) FIFO queue
-- Event-driven worker wakeups
-- Draining shutdown
+| Value | Meaning |
+| ---: | --- |
+| `0` | Immediate attempt or check |
+| finite value | Maximum wait from call entry, in milliseconds |
+| `THREADPOOL_INFINITE` | No deadline |
-**Producer-Consumer Thread Pool**
-- Fixed-size circular queue (1024 items by default, configurable)
-- Event-driven not-empty/not-full signalling
-- Timeout-aware bounded submission and draining shutdown
+The Simple queue is unbounded, so its `TryQueue` timeout is present for API
+symmetry and capacity cannot time out. For the bounded pool, `TryQueue` returns
+`False` if no slot becomes available before the deadline. The legacy `Queue`
+calls use a bounded compatibility wait and raise `EQueueFullException` when it
+expires.
+> [!WARNING]
+> โฑ๏ธ **Coordination matters:** `WaitForAll` does not stop unrelated producer
+> threads from submitting more work. Coordinate producers first, or call
+> `Shutdown` to close admission before draining. Tasks and `OnError` callbacks
+> also have no automatic execution deadline; add cancellation or
+> application-level timeouts where needed.
-## โ ๏ธ Common Mistakes
+## Error handling
-### 1. Freeing an object before `WaitForAll`
+Task exceptions are caught so a worker failure does not terminate the pool.
```pascal
-// WRONG โ MyObject may be freed while worker threads are still calling its methods
-MyObject := TMyClass.Create;
-GlobalThreadPool.Queue(@MyObject.DoWork);
-MyObject.Free; // freed too early!
-GlobalThreadPool.WaitForAll;
-
-// CORRECT โ always wait before freeing
-MyObject := TMyClass.Create;
-try
- GlobalThreadPool.Queue(@MyObject.DoWork);
- GlobalThreadPool.WaitForAll; // wait first
-finally
- MyObject.Free; // safe to free now
+var
+ MessageText: string;
+begin
+ Pool.ClearErrors;
+ Pool.Queue(@RiskyWork);
+ Pool.WaitForAll;
+
+ for MessageText in Pool.Errors do
+ WriteLn(MessageText);
end;
```
-### 2. Forgetting `WaitForAll`
+- `LastError` is the most recent message.
+- `Errors` is an oldest-first snapshot capped at 1000 entries.
+- `ErrorCount` reports the stored count.
+- `ClearErrors` clears the collection and `LastError`.
+- `OnError` fires synchronously on the worker that caught the exception. Keep
+ callbacks short, bounded, and thread-safe.
-Without `WaitForAll`, your program may exit (and destroy the pool) while tasks are still running, causing access violations or silent data loss.
+## Supported task forms
-```pascal
-// WRONG
-for i := 0 to 99 do
- GlobalThreadPool.Queue(@ProcessItem, i);
-// program exits here, tasks may never finish
-
-// CORRECT
-for i := 0 to 99 do
- GlobalThreadPool.Queue(@ProcessItem, i);
-GlobalThreadPool.WaitForAll;
-```
+| Form | Example |
+| --- | --- |
+| Procedure | `Pool.Queue(@DoWork)` |
+| Object method | `Pool.Queue(@Worker.DoWork)` |
+| Indexed procedure | `Pool.Queue(@ProcessItem, I)` |
+| Indexed object method | `Pool.Queue(@Worker.ProcessItem, I)` |
-### 3. `LastError` only keeps the most recent error
+Objects must remain alive until all queued methods that reference them have
+finished. Call `WaitForAll` before freeing a callback target.
-`LastError` is overwritten on every exception. If multiple tasks fail and you
-only check `LastError`, you see just the last one. Use the `Errors` collection
-to capture all of them โ see [Capturing all task errors](#capturing-all-task-errors-v070) below.
+## Installation
-```pascal
-// Queue several tasks that might fail
-for i := 0 to 9 do
- Pool.Queue(@RiskyProc, i);
-Pool.WaitForAll;
-
-// Only the LAST exception is in LastError...
-if Pool.LastError <> '' then
- WriteLn('Most recent failure: ', Pool.LastError);
-// ...but Errors has them all:
-WriteLn(Pool.ErrorCount, ' task(s) failed in total');
-Pool.ClearErrors;
-```
+The library has no external dependencies.
-### 4. Freeing the global pool manually
+1. Add [`src`](src/) to the project's unit search path, or install
+ [`package/lazarus/threadpool_fp.lpk`](package/lazarus/threadpool_fp.lpk) in
+ Lazarus.
+2. Add `ThreadPool.Simple` or `ThreadPool.ProducerConsumer` to the `uses` clause.
+3. On Unix-like systems, put `cthreads` first as shown in the quick start.
-`GlobalThreadPool` is managed by the unit's `initialization`/`finalization` blocks. Do **not** call `GlobalThreadPool.Free` โ let the runtime clean it up.
+Requirements:
-```pascal
-// WRONG
-GlobalThreadPool.Free; // double-free at program exit!
+- Free Pascal 3.2.2 or later
+- Lazarus 4.0 or later when using the package or project files
-// CORRECT โ just use it; finalization handles cleanup
-GlobalThreadPool.Queue(@MyProc);
-GlobalThreadPool.WaitForAll;
-```
+## Examples
-## ๐ง Planned/In Progress
+| 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 |
-- Result-bearing task handles/futures
-- Chunked `ParallelFor` helpers
-- Additional platform and long-running soak coverage
+More focused samples cover:
-## ๐ค Contributing
+- computation: [`SimpleSquareNumbers`](examples/SimpleSquareNumbers/) and
+ [`ProdConSquareNumbers`](examples/ProdConSquareNumbers/);
+- stateful processing: [`SimpleThreadpoolDemo`](examples/SimpleThreadpoolDemo/),
+ [`SimpleWordCounter`](examples/SimpleWordCounter/), and
+ [`ProdConMessageProcessor`](examples/ProdConMessageProcessor/);
+- advanced callbacks: [`SimpleErrorHandling`](examples/SimpleErrorHandling/);
+- real I/O: [`ParallelFileHasher`](examples/ParallelFileHasher/) and
+ [`ParallelUrlFetcher`](examples/ParallelUrlFetcher/).
-Contributions are welcome โ bug reports, docs, examples, and code. See
-[CONTRIBUTING.md](CONTRIBUTING.md) for how to build, test, and submit changes.
+## Documentation
-## ๐ Acknowledgments
+| Document | Purpose |
+| --- | --- |
+| [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 |
+| [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 |
+| [Changelog](CHANGELOG.md) | Full version history |
-Special thanks to the Free Pascal and Lazarus communities and the creators of the threading libraries mentioned above for inspiration!
-- [Mormot2 Threading Library](https://github.com/synopse/mORMot2)
-- [ezthreads](https://github.com/mr-highball/ezthreads)
-- [OmniThreadLibrary](https://github.com/gabr42/OmniThreadLibrary)
+The banner's editable source is
+[`docs/assets/threadpool-banner.svg`](docs/assets/threadpool-banner.svg); the
+README displays its synchronized PNG render.
+## Build and test
-## ๐ License
-
-This project is licensed under the MIT License - see the [LICENSE](LICENSE.md) file for details.
+```bash
+lazbuild package/lazarus/threadpool_fp.lpk
+lazbuild tests/TestRunner.lpi
+./tests/TestRunner -a -p --format=plain
+```
-## ๐ Changelog
+On Windows, run `tests/TestRunner.exe` in the final command. CI builds the
+package, tests, benchmark, and every example on Windows and Linux.
-See [CHANGELOG.md](CHANGELOG.md) for the full version history.
+## Contributing
----
+Bug reports, documentation improvements, examples, and code contributions are
+welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for the build, style, and pull
+request workflow.
-๐ก **More Tip**: Check out the examples directory for more usage patterns!
+## License
+ThreadPool for Free Pascal is available under the [MIT License](LICENSE.md).
diff --git a/docs/CHEATSHEET.md b/docs/CHEATSHEET.md
new file mode 100644
index 0000000..8a5ad06
--- /dev/null
+++ b/docs/CHEATSHEET.md
@@ -0,0 +1,157 @@
+# ThreadPool for Free Pascal โ cheat sheet
+
+Quick reference for v0.8.5. For full contracts, use the
+[Simple API](ThreadPool.Simple-API.md) or
+[Producer-Consumer API](ThreadPool.ProducerConsumer-API.md).
+
+## Choose a pool
+
+| Need | Use |
+| --- | --- |
+| Straightforward fire-and-forget work and an unbounded FIFO | `ThreadPool.Simple` |
+| A ready-to-use process-wide instance | `GlobalThreadPool` from `ThreadPool.Simple` |
+| Bounded memory and explicit queue saturation handling | `ThreadPool.ProducerConsumer` |
+| Producer backpressure with a submission deadline | `TProducerConsumerThreadPool.TryQueue` |
+
+## Import correctly
+
+On Linux and macOS, `cthreads` must be the first unit in the program's `uses`
+clause. Windows does not need it.
+
+```pascal
+uses
+ {$IFDEF UNIX}
+ cthreads,
+ {$ENDIF}
+ ThreadPool.Simple; // or ThreadPool.ProducerConsumer
+```
+
+## Create a pool
+
+```pascal
+// Managed singleton; do not free it yourself.
+GlobalThreadPool.Queue(@DoWork);
+
+// Private unbounded pool.
+SimplePool := TSimpleThreadPool.Create(4);
+
+// Private bounded pool: worker count, queue capacity.
+BoundedPool := TProducerConsumerThreadPool.Create(4, 1024);
+```
+
+Pass `0` as the worker count to use `TThread.ProcessorCount`. The library always
+creates at least four workers; positive requests are capped at
+`2 * TThread.ProcessorCount` before that minimum is applied.
+
+## Queue work
+
+| Task form | Call |
+| --- | --- |
+| Procedure | `Pool.Queue(@DoWork)` |
+| Object method | `Pool.Queue(@Worker.DoWork)` |
+| Indexed procedure | `Pool.Queue(@ProcessItem, Index)` |
+| Indexed object method | `Pool.Queue(@Worker.ProcessItem, Index)` |
+
+The indexed callback signatures are:
+
+```pascal
+procedure ProcessItem(Index: Integer);
+procedure TWorker.ProcessItem(Index: Integer);
+```
+
+## Handle bounded-queue saturation
+
+Prefer `TryQueue` when using the Producer-Consumer pool:
+
+```pascal
+if not Pool.TryQueue(@DoWork, 50) then
+ WriteLn('Queue remained full for 50 ms');
+```
+
+`Queue(...)` remains available, but it uses a bounded compatibility wait and
+raises `EQueueFullException` if that wait expires. The Simple pool is unbounded,
+so its `TryQueue` timeout exists for API symmetry and capacity does not time out.
+
+## Wait and shut down
+
+```pascal
+Pool.WaitForAll; // wait indefinitely
+
+if not Pool.WaitForAll(250) then
+ WriteLn('Work remains after 250 ms');
+
+Pool.Shutdown; // stop admission, drain, join workers
+```
+
+Timeouts are milliseconds:
+
+| Value | Meaning |
+| ---: | --- |
+| `0` | Immediate attempt or state check |
+| finite value | Maximum wait from call entry |
+| `THREADPOOL_INFINITE` | No deadline |
+
+After shutdown begins, `Queue` and `TryQueue` raise `EThreadPoolShutdown`.
+Repeated `Shutdown` calls are safe. Do not call `Shutdown` from one of the
+pool's own worker tasks.
+
+## Read task errors
+
+Worker exceptions are captured; they do not terminate the pool.
+
+```pascal
+var
+ MessageText: string;
+begin
+ Pool.ClearErrors;
+ Pool.Queue(@RiskyWork);
+ Pool.WaitForAll;
+
+ for MessageText in Pool.Errors do
+ WriteLn(MessageText);
+end;
+```
+
+- `LastError` is only the most recent message.
+- `Errors` is an oldest-first snapshot, capped at `MAX_STORED_ERRORS = 1000`.
+- `ErrorCount` reports the stored count.
+- `ClearErrors` clears both the collection and `LastError`.
+- `OnError` runs synchronously on the worker that caught the exception; keep the
+ handler short, bounded, and thread-safe.
+
+## Keep objects alive
+
+```pascal
+Worker := TWorker.Create;
+try
+ Pool.Queue(@Worker.DoWork);
+ Pool.WaitForAll; // wait before freeing the callback target
+finally
+ Worker.Free;
+end;
+```
+
+Also remember:
+
+- Do not free `GlobalThreadPool`; its unit owns it.
+- `WaitForAll` is not an admission barrier for unrelated producers. Coordinate
+ producers first, or use `Shutdown` to close admission before draining.
+- Tasks and `OnError` callbacks have no automatic execution deadline. Add
+ cancellation or application-level timeouts to operations that may block.
+
+## Build and test
+
+```bash
+lazbuild package/lazarus/threadpool_fp.lpk
+lazbuild tests/TestRunner.lpi
+./tests/TestRunner -a -p --format=plain
+```
+
+On Windows, run `tests/TestRunner.exe` in the final command.
+
+## More detail
+
+- [README](../README.md)
+- [Examples](../examples/)
+- [v0.8.5 release notes](release-notes-v0.8.5.md)
+- [Changelog](../CHANGELOG.md)
diff --git a/docs/assets/threadpool-banner.png b/docs/assets/threadpool-banner.png
new file mode 100644
index 0000000..0a4883f
Binary files /dev/null and b/docs/assets/threadpool-banner.png differ
diff --git a/docs/assets/threadpool-banner.svg b/docs/assets/threadpool-banner.svg
new file mode 100644
index 0000000..f383d20
--- /dev/null
+++ b/docs/assets/threadpool-banner.svg
@@ -0,0 +1,134 @@
+
diff --git a/docs/release-notes-v0.8.5.md b/docs/release-notes-v0.8.5.md
new file mode 100644
index 0000000..91aa4e9
--- /dev/null
+++ b/docs/release-notes-v0.8.5.md
@@ -0,0 +1,40 @@
+# 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.svg`](assets/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`](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](../README.md) |
+| Recall a call or safety rule | [Cheat sheet](CHEATSHEET.md) |
+| Explore every public member | [Simple API](ThreadPool.Simple-API.md) or [Producer-Consumer API](ThreadPool.ProducerConsumer-API.md) |
+| Understand internals | [Simple technical guide](ThreadPool.Simple-Technical.md) or [Producer-Consumer technical guide](ThreadPool.ProducerConsumer-Technical.md) |
+| Upgrade from an earlier lifecycle model | [v0.8.0 release notes](release-notes-v0.8.0.md) |
+
+## 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](../CHANGELOG.md) for the complete version history.
diff --git a/package/lazarus/threadpool_fp.lpk b/package/lazarus/threadpool_fp.lpk
index 7e6bd9b..1117a3e 100644
--- a/package/lazarus/threadpool_fp.lpk
+++ b/package/lazarus/threadpool_fp.lpk
@@ -17,50 +17,14 @@
-
+Both pools provide draining shutdown, timeout-aware waits, four task forms, and captured worker errors. See README.md and docs/CHEATSHEET.md for usage."/>
-
+