{"record":{"id":"397e8e1b896ee27d","repo":"microsoft/garnet","slug":"failed-to-schedule-work","errorCode":null,"errorMessage":"Failed to schedule work: {}","messagePattern":"Failed to schedule work: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"libs/storage/Tsavorite/cc/src/device/file_windows.cc","lineNumber":183,"sourceCode":"  ::CloseThreadpool(pool_);\n\n  delete callback_environment_;\n}\n\nStatus WindowsPtpThreadPool::Schedule(Task task, void* task_parameters) {\n  auto info = alloc_context<TaskInfo>(sizeof(TaskInfo));\n  if(!info.get()) return Status::OutOfMemory;\n  new(info.get()) TaskInfo();\n\n  info->task = task;\n  info->task_parameters = task_parameters;\n\n  PTP_WORK_CALLBACK ptp_callback = TaskStartSpringboard;\n  PTP_WORK work = CreateThreadpoolWork(ptp_callback, info.get(), callback_environment_);\n  if(!work) {\n    std::stringstream ss;\n    ss << \"Failed to schedule work: \" << FormatWin32AndHRESULT(::GetLastError());\n    fprintf(stderr, \"%s\\n\", ss.str().c_str());\n    return Status::Aborted;\n  }\n  SubmitThreadpoolWork(work);\n  info.release();\n\n  return Status::Ok;\n}\n\nvoid CALLBACK WindowsPtpThreadPool::TaskStartSpringboard(PTP_CALLBACK_INSTANCE instance,\n    PVOID parameter, PTP_WORK work) {\n  auto info = make_context_unique_ptr<TaskInfo>(reinterpret_cast<TaskInfo*>(parameter));\n  info->task(info->task_parameters);\n  CloseThreadpoolWork(work);\n}\n\nStatus ThreadPoolFile::Open(FileCreateDisposition create_disposition, const FileOptions& options,\n                            ThreadPoolIoHandler* handler, bool* exists) {\n  DWORD flags = FILE_FLAG_RANDOM_ACCESS | FILE_FLAG_OVERLAPPED;","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/microsoft/garnet/blob/951b0fc6838721f89d102c2bbe1b914e8d39d700/libs/storage/Tsavorite/cc/src/device/file_windows.cc#L165-L201","documentation":"Returned by WindowsPtpThreadPool::Schedule when CreateThreadpoolWork returns NULL. The pool allocates a TaskInfo, tries to register a work object against the Win32 thread pool bound in the constructor; on failure it formats FormatWin32AndHRESULT(GetLastError()), prints to stderr, and returns Status::Aborted (note: Aborted, not IOError). The TaskInfo is freed by alloc_context's deleter; no work runs.","triggerScenarios":"CreateThreadpoolWork fails: the thread pool or its cleanup group has already been closed/destroyed (e.g. Schedule called after the WindowsPtpThreadPool destructor began closing members), the callback_environment_ is invalid, or the system is out of resources (memory / handles / thread quota). GetLastError typically yields ERROR_INVALID_PARAMETER (environment torn down) or a resource-exhaustion code.","commonSituations":"Submitting tasks during or after device/thread-pool shutdown; a destructor racing with in-flight Schedule calls on another thread; running under a low handle/quota job object; very high async checkpoint/grow concurrency exhausting the pool; reusing a FASTER instance whose thread pool was already disposed.","solutions":["Stop all callers of Schedule (quiesce pending async ops, e.g. the device's numPending) before destroying the thread pool / closing cleanup group members.","Check the Status return from Schedule (and from ReadAsync/WriteAsync which surface it) and treat Status::Aborted as fatal-to-operation rather than continuing.","Inspect the HRESULT in the stderr line — ERROR_INVALID_PARAMETER points to environment teardown ordering; resource codes point to quota/memory.","If genuinely transient (brief resource pressure), retry Schedule a bounded number of times with backoff, otherwise propagate the failure to the caller."],"exampleFix":"// before:\n// Status s = pool.Schedule(task, ctx);\n// after:\nStatus s = pool.Schedule(task, ctx);\nif (s != Status::Ok) {\n  // Aborted => threadpool torn down or out of resources; do not retry indefinitely\n  FinishRequest(ctx, s);\n  return s;\n}","handlingStrategy":"retry","validationCode":"// Validate the thread pool is live before submitting work.\nif (pool == nullptr || pool->IsClosed()) return Status::Aborted;\n// Then check the returned Status from Schedule; Aborted means do not silently continue.","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Quiesce all Schedule callers (drain pending ops) before the thread pool destructor closes cleanup-group members.","Always check the Status from Schedule/ReadAsync/WriteAsync; treat Status::Aborted as terminal, not ignorable.","Capture and decode the HRESULT in the stderr line to distinguish teardown (ERROR_INVALID_PARAMETER) from resource exhaustion.","Retry Schedule only a bounded number of times with backoff; do not retry unchanged after a teardown-coded failure."],"tags":["tsavorite","cpp","windows","threadpool","win32","scheduling"],"backgroundTag":null,"analyzedSha":"951b0fc6838721f89d102c2bbe1b914e8d39d700","analyzedAt":"2026-08-13T19:01:32.939Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}