Notice that we don't recycle the Task in this function, let TimerThread::run do it. The side effect is that we may allocate many unscheduled tasks before TimerThread wakes up. The number is approximately qps * timeout_s. Under the precondition that ResourcePool caches 128K for each thread, with some further calculations, we can conclude that in a RPC scenario: when timeout / latency < 2730 (
| 257 | // between timeout and latency in most RPC scenarios, this is why we don't |
| 258 | // try to reuse tasks right now inside unschedule() with more complicated code. |
| 259 | int TimerThread::unschedule(TaskId task_id) { |
| 260 | const butil::ResourceId<Task> slot_id = slot_of_task_id(task_id); |
| 261 | Task* const task = butil::address_resource(slot_id); |
| 262 | if (task == NULL) { |
| 263 | LOG(ERROR) << "Invalid task_id=" << task_id; |
| 264 | return -1; |
| 265 | } |
| 266 | const uint32_t id_version = version_of_task_id(task_id); |
| 267 | uint32_t expected_version = id_version; |
| 268 | // This CAS is rarely contended, should be fast. |
| 269 | // The acquire fence is paired with release fence in Task::run_and_delete |
| 270 | // to make sure that we see all changes brought by fn(arg). |
| 271 | if (task->version.compare_exchange_strong( |
| 272 | expected_version, id_version + 2, |
| 273 | butil::memory_order_acquire)) { |
| 274 | return 0; |
| 275 | } |
| 276 | return (expected_version == id_version + 1) ? 1 : -1; |
| 277 | } |
| 278 | |
| 279 | bool TimerThread::Task::run_and_delete() { |
| 280 | const uint32_t id_version = version_of_task_id(task_id); |