   // possible to provide a safer API that doesn't leak by default without inducing an extra pointer
   // dereference on critical paths. :-(
 
+  let totalThreadCount: Int
+  let workerThreadCount: Int
+  let coprimes: [Int]
+  let queues: [NonBlockingThreadPool<Environment>.Queue]
+  let condition: NonblockingCondition<Environment>
+
   var rng: PCGRandomNumberGenerator
 
   var isCancelled: Bool { pool.cancelled }
 
   func steal() -> Task? {
     let r = rng.next()
-    var selectedThreadId = Int(r.reduced(into: UInt64(pool.totalThreadCount)))
-    let step = pool.coprimes[Int(r.reduced(into: UInt64(pool.coprimes.count)))]
+    var selectedThreadId = Int(r.reduced(into: UInt64(totalThreadCount)))
+    let step = coprimes[Int(r.reduced(into: UInt64(coprimes.count)))]
     assert(
-      step < pool.totalThreadCount, "step: \(step), pool threadcount: \(pool.totalThreadCount)")
-
-    for i in 0..<pool.totalThreadCount {
+      step < totalThreadCount, "step: \(step), pool threadcount: \(totalThreadCount)")
+
+    for i in 0..<totalThreadCount {
       assert(
-        selectedThreadId < pool.totalThreadCount,
-        "\(selectedThreadId) is too big on iteration \(i); max: \(pool.totalThreadCount), step: \(step)"
+        selectedThreadId < totalThreadCount,
+        "\(selectedThreadId) is too big on iteration \(i); max: \(totalThreadCount), step: \(step)"
       )
-      if let task = pool.queues[selectedThreadId].popBack() {
+      if let task = queues[selectedThreadId].popBack() {
         return task
       }
       selectedThreadId += step
-      if selectedThreadId >= pool.totalThreadCount {
-        selectedThreadId -= pool.totalThreadCount
+      if selectedThreadId >= totalThreadCount {
+        selectedThreadId -= totalThreadCount
       }
     }
     return nil
   }
 
   func spin() -> Task? {
-    let spinCount = pool.threads.count > 0 ? Constants.spinCount / pool.threads.count : 0
+    let spinCount = workerThreadCount > 0 ? Constants.spinCount / workerThreadCount : 0
 
     if pool.shouldStartSpinning() {
       // Call steal spin_count times; break if steal returns something.

  var cancelledStorage: AtomicUInt64
  var blockedCountStorage: AtomicUInt64
  var spinningState: AtomicUInt64
  var condition: NonblockingCondition<Environment>
  var waitingMutex: [Environment.ConditionMutex]  // TODO: modify condition to add per-thread wakeup
  var externalWaitingMutex: Environment.ConditionMutex
  var threads: [Environment.Thread]

  private let perThreadKey = Environment.ThreadLocalStorage.makeKey(

  private func findNonEmptyQueueIndex() -> Int? {
    let r = rng.next()
    let increment =
      totalThreadCount == 1 ? 1 : coprimes[Int(r.reduced(into: UInt64(coprimes.count)))]
    var threadIndex = Int(r.reduced(into: UInt64(totalThreadCount)))
    for _ in 0..<totalThreadCount {
      if !queues[threadIndex].isEmpty { return threadIndex }
      threadIndex += increment
      if threadIndex >= totalThreadCount {
        threadIndex -= totalThreadCount
      }
    }
    return nil
