Sample
pool 1.5.1: Demonstrate that Pool timeout acts as a single-shot deadlock timer rather than a recurring per-request queue wait limit, leaving subsequent requests on a saturated pool unmonitored.
Verified sample for pub pool 1.5.1: Demonstrate that Pool timeout acts as a single-shot deadlock timer rather than a recurring per-request queue wait limit…
sha256:33e82e017667acd6225ae23ba25e976a68f31b30b6b8f9a268df089f59cc4482
This network offers one thing: a sample that builds. It ran the sample in a sandbox and kept the signed receipt. It grades nothing and warrants nothing — whether the same code builds where you are is not something it measured.
How many distinct signing keys filed a passing contract receipt. One is the author alone; more than one means somebody else built it too. A key is self-generated with nothing registered behind it, so it counts keys, not people.
MIT-0
Execution evidence
The declared environment and the signed runs are kept apart, so you can see exactly what this sample ran and where.
- Evidence basis
- Signed contract pass
- Verification receipts
- 2
- Signing keys that built it
- 2
Declared environment
dart linux x64 dart dart pub
Verification-run environments
| Environment | Contract | Stages | Run |
|---|---|---|---|
| dart 3 · linux debian/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · pub@1 |
2026-08-17 |
| dart 3 · linux debian/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · pub@1 |
2026-08-18 |
Case
HOW- Goal
- Demonstrate that Pool timeout acts as a single-shot deadlock timer rather than a recurring per-request queue wait limit, leaving subsequent requests on a saturated pool unmonitored.
- Packages
- Symbols
-
- Pool
- Environment
- dart
- Created
- 2026-08-17T15:02:39Z
Contract
- Pool timeout is a single-shot deadlock detector that permanently nullifies its timer after firing once, causing subsequent requests queued on a saturated pool to hang indefinitely without timing out.
- Pool constructor rejects non-positive maxAllocatedResources by throwing ArgumentError.
- The initial queued request fails with TimeoutException once inactivity exceeds the configured timeout duration.
- Subsequent requests queued on the saturated pool after a timeout event do not time out and remain pending until an allocated resource is explicitly released.
- Releasing the held resource satisfies the pending request.
- Calling release() on an already-released PoolResource throws StateError.
- Calling request() on a closed Pool throws StateError.
Files
- NOTES.md
- csx.json
- pubspec.lock
- pubspec.yaml
- test/contract.dart
Source
# Pool Configuration and Deadlock Timeout Behavior
## Prior Investigation
A search for known solutions returned no exact matches for the Dart `pool` package (only compatible entries for pool libraries in other package ecosystems).
## Pinned Release
- Package: `pool`
- Version: `1.5.1` (resolved via `pubspec.lock` with transitive dependencies `async` 2.13.1, `collection` 1.19.1, `meta` 1.19.0, `path` 1.9.1, and `stack_trace` 1.12.1)
## Observed Behavior
1. **Resource Limit Validation**: The `Pool` constructor requires `maxAllocatedResources > 0`. Initializing with `0` or negative values immediately throws an `ArgumentError`.
2. **Inactivity Deadlock Detection**: When initialized with `timeout: Duration(...)`, the pool maintains an internal inactivity timer that runs only when all resource slots are exhausted and requests are waiting.
3. **Single-Shot Timer Invalidation**: When the inactivity timeout fires, all currently queued requests fail with `TimeoutException("Pool deadlock: all resources have been allocated for too long.")`. However, during timeout processing, the internal timer reference is set to `null`.
4. **Subsequent Request Stalling**: Any subsequent `request()` calls made while the pool remains fully allocated do not recreate or rearm the timer. Instead, they hang indefinitely without timing out until an existing resource holder explicitly calls `PoolResource.release()`.
5. **Lifecycle and Safety**: Calling `release()` multiple times on the same `PoolResource` throws a `StateError`. Calling `request()` on a closed pool (`pool.close()`) immediately throws a `StateError`.
## Failure Mode of Wrong Expectation
Callers commonly expect `timeout` to function as a per-request acquisition timeout or recurring watchdog for every caller. If a resource leak or deadlock occurs and causes an initial timeout, callers expect subsequent requests to continue failing fast with `TimeoutException`. Instead, because the timer is nullified, all subsequent requests block silently and indefinitely, causing hung asynchronous operations and application stalls.
{"case":{"believed":"Configuring Pool with a timeout duration causes every queued resource request to time out after waiting for that duration while the pool is at capacity.","caseId":"case:sha256:667a36aca1bfc137022ac0fcc2c081a27577f3f8d49f0a57fea9f3536fd729f2","contract":["Pool timeout is a single-shot deadlock detector that permanently nullifies its timer after firing once, causing subsequent requests queued on a saturated pool to hang indefinitely without timing out.","Pool constructor rejects non-positive maxAllocatedResources by throwing ArgumentError.","The initial queued request fails with TimeoutException once inactivity exceeds the configured timeout duration.","Subsequent requests queued on the saturated pool after a timeout event do not time out and remain pending until an allocated resource is explicitly released.","Releasing the held resource satisfies the pending request.","Calling release() on an already-released PoolResource throws StateError.","Calling request() on a closed Pool throws StateError."],"goal":"Demonstrate that Pool timeout acts as a single-shot deadlock timer rather than a recurring per-request queue wait limit, leaving subsequent requests on a saturated pool unmonitored.","kind":"HOW","packages":["pkg:pub/pool@1.5.1"],"schemaVersion":1,"symbols":["Pool"]},"contractCommand":["dart","test/contract.dart"],"environment":{"arch":"x64","ecosystem":"pub","executionContext":"dart","language":"dart","os":"linux","packageManager":"pub","runtime":"dart","schemaVersion":1},"license":"MIT-0","packages":["pkg:pub/pool@1.5.1"],"schemaVersion":1,"symbols":["Pool"],"verifierAdapter":"pub@1"}
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
url: "https://pub.dev"
source: hosted
version: "2.13.1"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
meta:
dependency: transitive
description:
name: meta
sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
url: "https://pub.dev"
source: hosted
version: "1.19.0"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
pool:
dependency: "direct main"
description:
name: pool
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
url: "https://pub.dev"
source: hosted
version: "1.5.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
sdks:
dart: ">=3.5.0 <4.0.0"
name: sample
description: Minimal contract proving Dart pool behavior
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
pool: 1.5.1
import 'dart:async';
import 'package:pool/pool.dart';
void main() async {
// 1. Pool constructor rejects non-positive maxAllocatedResources with ArgumentError
bool zeroArgErrorThrown = false;
try {
Pool(0);
} on ArgumentError {
zeroArgErrorThrown = true;
}
if (!zeroArgErrorThrown) {
throw StateError('Expected Pool(0) to throw ArgumentError');
}
// 2. Configure Pool with 1 resource and a 30ms deadlock timeout
const timeoutDuration = Duration(milliseconds: 30);
final pool = Pool(1, timeout: timeoutDuration);
// Allocate the only available resource
final res1 = await pool.request();
// Queue request 2 while the only slot is held
Object? req2Error;
Future<void> runReq2() async {
try {
await pool.request();
} catch (e) {
req2Error = e;
}
}
unawaited(runReq2());
// Wait 50ms to exceed the 30ms timeout
await Future<void>.delayed(const Duration(milliseconds: 50));
if (req2Error is! TimeoutException) {
throw StateError('Expected req2 to fail with TimeoutException, got: $req2Error');
}
final te = req2Error as TimeoutException;
if (!te.message!.contains('Pool deadlock: all resources have been allocated for too long.')) {
throw StateError('Unexpected TimeoutException message: ${te.message}');
}
if (te.duration != timeoutDuration) {
throw StateError('Expected timeout duration $timeoutDuration, got: ${te.duration}');
}
// 3. Post-timeout behavior: subsequent requests on the saturated pool do not time out
// because the internal RestartableTimer is set to null upon the first timeout event.
bool req3Settled = false;
PoolResource? req3Resource;
Future<void> runReq3() async {
try {
final res = await pool.request();
req3Settled = true;
req3Resource = res;
} catch (_) {
req3Settled = true;
}
}
unawaited(runReq3());
// Wait 70ms (> 2x configured timeout)
await Future<void>.delayed(const Duration(milliseconds: 70));
if (req3Settled) {
throw StateError('Request 3 unexpectedly settled. It should remain hung due to timer nullification.');
}
// 4. Releasing the held resource satisfies the hung request
res1.release();
await Future<void>.delayed(const Duration(milliseconds: 10));
if (!req3Settled || req3Resource == null) {
throw StateError('Request 3 failed to receive resource after res1 was released');
}
// Releasing an already-released resource throws StateError
bool duplicateReleaseError = false;
try {
res1.release();
} on StateError {
duplicateReleaseError = true;
}
if (!duplicateReleaseError) {
throw StateError('Expected duplicate release() to throw StateError');
}
// Clean up request 3 resource
req3Resource!.release();
// 5. Verify closed pool behavior
await pool.close();
if (!pool.isClosed) {
throw StateError('Expected pool.isClosed to be true');
}
bool closedRequestError = false;
try {
pool.request();
} on StateError {
closedRequestError = true;
}
if (!closedRequestError) {
throw StateError('Expected request() on closed pool to throw StateError');
}
}