{"id":31658520,"url":"https://github.com/couchbase/ep-engine","last_synced_at":"2025-10-07T15:26:11.895Z","repository":{"id":2228357,"uuid":"3180598","full_name":"couchbase/ep-engine","owner":"couchbase","description":"Eventually Persistent Couchbase Data Layer.","archived":false,"fork":false,"pushed_at":"2018-08-01T13:12:05.000Z","size":32027,"stargazers_count":25,"open_issues_count":2,"forks_count":29,"subscribers_count":10,"default_branch":"master","last_synced_at":"2023-04-09T21:02:06.462Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://www.couchbase.org","language":"C++","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/couchbase.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-01-14T21:55:39.000Z","updated_at":"2022-11-27T08:51:58.000Z","dependencies_parsed_at":"2022-07-31T12:08:03.256Z","dependency_job_id":null,"html_url":"https://github.com/couchbase/ep-engine","commit_stats":null,"previous_names":[],"tags_count":106,"template":null,"template_full_name":null,"purl":"pkg:github/couchbase/ep-engine","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/couchbase%2Fep-engine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/couchbase%2Fep-engine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/couchbase%2Fep-engine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/couchbase%2Fep-engine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/couchbase","download_url":"https://codeload.github.com/couchbase/ep-engine/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/couchbase%2Fep-engine/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278797377,"owners_count":26047641,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-07T02:00:06.786Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-07T15:26:10.032Z","updated_at":"2025-10-07T15:26:11.887Z","avatar_url":"https://github.com/couchbase.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Eventually Persistent Engine\n## Threads\nCode in ep-engine is executing in a multithreaded environment, two classes of\nthread exist.\n\n1. memcached's threads, for servicing a client and calling in via the\n[engine API] (https://github.com/couchbase/memcached/blob/master/include/memcached/engine.h)\n2. ep-engine's threads, for running tasks such as the document expiry pager\n(see subclasses of `GlobalTasks`).\n\n## Synchronisation Primitives\n\nThere are two mutual-exclusion primitives available in ep-engine (in\naddition to those provided by the C++ standard library):\n\n1. `RWLock` shared, reader/writer lock - [rwlock.h](./src/rwlock.h)\n2. `SpinLock` 1-byte exclusive lock - [atomix.h](./src/atomic.h)\n\nA condition-variable is also available called `SyncObject`\n[syncobject.h](./src/syncobject.h). `SyncObject` glues a `std::mutex` and\n`std::condition_variable` together in one object.\n\nThese primitives are managed via RAII wrappers - [locks.h](./src/locks.h).\n\n1. `LockHolder` - a deprecated alias for std::lock_guard\n2. `MultiLockHolder` - for acquiring an array of `std::mutex` or `SyncObject`.\n\n### Mutex\nThe general style is to create a `std::lock_guard` when you need to acquire a\n`std::mutex`, the constructor will acquire and when the `lock_guard` goes out of\nscope, the destructor will release the `std::mutex`. For certain use-cases the\ncaller can explicitly lock/unlock a `std::mutex` via the `std::unique_lock`\nclass.\n\n```c++\nstd::mutex mutex;\nvoid example1() {\n    std::lock_guard\u003cstd::mutex\u003e lockHolder(mutex);\n    ...\n    return;\n}\n\nvoid example2() {\n    std::unique_lock\u003cstd::mutex\u003e lockHolder(mutex);\n    ...\n    lockHolder.unlock();\n    ...\n    lockHolder.lock();\n    ...\n    return;\n}\n```\n\nA `MultiLockHolder` allows an array of locks to be conveniently acquired and\nreleased, and similarly to `LockHolder` the caller can choose to manually\nlock/unlock at any time (with all locks locked/unlocked via one call).\n\n```c++\nstd::mutex mutexes[10];\nObject objects[10];\nvoid foo() {\n    MultiLockHolder lockHolder(\u0026mutexes, 10);\n    for (int ii = 0; ii \u003c 10; ii++) {\n        objects[ii].doStuff();\n    }\n    return;\n}\n```\n\n### RWLock\n\n`RWLock` allows many readers to acquire it and exclusive access for a writer.\nLike a std::mutex `RWLock` can be used with a std::lock_guard. The RWLock can\neither be explicitly casted to a `ReaderLock` / `WriterLock` through its\n`reader()` and `writer()` member functions or you can rely on the implicit\nconversions used by the `lock_guard` constructor.\n\n```c++\nRWLock rwLock;\nObject thing;\n\nvoid foo1() {\n    std::lock_guard\u003cReaderLock\u003e rlh(rwLock);\n    if (thing.getData()) {\n    ...\n    }\n}\n\nvoid foo2() {\n    std::lock_guard\u003cWriterLock\u003e wlh(rwLock);\n    thing.setData(...);\n}\n```\n\n### SyncObject\n\n`SyncObject` inherits from `std::mutex` and is thus managed via a `LockHolder` or\n`MultiLockHolder`. The `SyncObject` provides the conditional-variable\nsynchronisation primitive enabling threads to block and be woken.\n\nThe wait/wakeOne/wake method is provided by the `SyncObject`.\n\nNote that `wake` will wake up a single blocking thread, `wakeOne` will wake up\nevery thread that is blocking on the `SyncObject`.\n\n```c++\nSyncObject syncObject;\nbool sleeping = false;\nvoid foo1() {\n    LockHolder lockHolder(\u0026syncObject);\n    sleeping = true;\n    syncObject.wait(); // the mutex is released and the thread put to sleep\n    // when wait returns the mutex is reacquired\n    sleeping = false;\n}\n\nvoid foo2() {\n    LockHolder lockHolder(\u0026syncObject);\n    if (sleeping) {\n        syncObject.notifyOne();\n    }\n}\n```\n\n### SpinLock\n\nA `SpinLock` uses a single byte for the lock and our own code to spin until the\nlock is acquired. The intention for this lock is for low contention locks.\n\nThe RAII pattern is just like for a mutex.\n\n\n```c++\nSpinLock spinLock;\nvoid example1() {\n    std::lock_guard\u003cSpinLock\u003e lockHolder(\u0026spinLock);\n    ...\n    return;\n}\n```\n\n### _UNLOCKED convention\n\nep-engine has a function naming convention that indicates the function should\nbe called with a lock acquired.\n\nFor example the following `doStuff_UNLOCKED` method indicates that it expect a\nlock to be held before the function is called. What lock should be acquired\nbefore calling is not defined by the convention.\n\n```c++\nvoid Object::doStuff_UNLOCKED() {\n}\n\nvoid Object::run() {\n    LockHolder lockHolder(\u0026mutex);\n    doStuff_UNLOCKED();\n    return;\n}\n```\n\n## Atomic / thread-safe data structures\n\nIn addition to the basic synchronization primitives described above,\nthere are also the following higher-level data structures which\nsupport atomic / thread-safe access from multiple threads:\n\n1. `AtomicQueue`: thread-safe, approximate-FIFO queue, optimized for\n   multiple-writers, one reader - [atomicqueue.h](./src/atomicqueue.h)\n2. `AtomicUnorderedMap` : thread-safe unordered map -\n   [atomic_unordered_map.h](./src/atomic_unordered_map.h)\n\n## Thread Local Storage (ObjectRegistry).\n\nThreads in ep-engine are servicing buckets and when a thread is dispatched to\nserve a bucket, the pointer to the `EventuallyPersistentEngine` representing\nthe bucket is placed into thread local storage, this avoids the need for the\npointer to be passed along the chain of execution as a formal parameter.\n\nBoth threads servicing frontend operations (memcached's threads) and ep-engine's\nown task threads will save the bucket's engine pointer before calling down into\nengine code.\n\nCalling `ObjectRegistry::onSwitchThread(enginePtr)` will save the `enginePtr`\nin thread-local-storage so that subsequent task code can retrieve the pointer\nwith `ObjectRegistry::getCurrentEngine()`.\n\n## Tasks\n\nA task is created by creating a sub-class (the `run()` method is the entry point\nof the task) of the `GlobalTask` class and it is scheduled onto one of 4 task\nqueue types. Each task should be declared in `src/tasks.defs.h` using the TASK\nmacro. Using this macro ensures correct generation of a task-type ID, priority,\ntask name and ultimately ensures each task gets its own scheduling statistics.\n\nThe recipe is simple.\n\n### Add your task's class name with its priority into `src/tasks.defs.h`\n * A lower value priority is 'higher'.\n```\nTASK(MyNewTask, 1) // MyNewTask has priority 1.\n```\n\n### Create your class and set its ID using `MY_TASK_ID`.\n\n```\nclass MyNewTask : public GlobalTask {\npublic:\n    MyNewTask(EventuallyPersistentEngine* e)\n        : GlobalTask(e/*engine/,\n                     MY_TASK_ID(MyNewTask),\n                     0.0/*snooze*/){}\n...\n```\n\n### Define pure-virtual methods in `MyNewTask`\n* run method\n\nThe run method is invoked when the task is executed. The method should return\ntrue if it should be scheduled again. If false is returned, the instance of the\ntask is never re-scheduled and will deleted once all references to the instance are\ngone.\n\n```\nbool run() {\n   // Task code here\n   return schedule again?;\n}\n```\n\n* Define the `getDescription` method to aid debugging and statistics.\n```\nstd::string getDescription() {\n    return \"A brief description of what MyNewTask does\";\n}\n```\n\n### Schedule your task to the desired queue.\n```\nExTask myNewTask = new MyNewTask(\u0026engine);\nmyNewTaskId = ExecutorPool::get()-\u003eschedule(myNewTask, NONIO_TASK_IDX);\n```\n\nThe 4 task queue types are:\n* Readers -  `READER_TASK_IDX`\n * Tasks that should primarily only read from 'disk'. They generally read from\nthe vbucket database files, for example background fetch of a non-resident document.\n* Writers (they are allowed to read too) `WRITER_TASK_IDX`\n * Tasks that should primarily only write to 'disk'. They generally write to\nthe vbucket database files, for example when flushing the write queue.\n* Auxilliary IO `AUXIO_TASK_IDX`\n * Tasks that read and write 'disk', but not necessarily the vbucket data files.\n* Non IO `NONIO_TASK_IDX`\n * Tasks that do not perform 'disk' I/O.\n\n### Utilise `snooze`\n\nThe snooze value of the task sets when the task should be executed. The initial snooze\nvalue is set when constructing `GlobalTask`. A value of 0.0 means attempt to execute\nthe task as soon as scheduled and 5.0 would be 5 seconds from being scheduled\n(scheduled meaning when `ExecutorPool::get()-\u003eschedule(...)` is called).\n\nThe `run()` function can also call `snooze(double snoozeAmount)` to set how long\nbefore the task is rescheduled.\n\nIt is **best practice** for most tasks to actually do a sleep forever from their run function:\n\n```\n  snooze(INT_MAX);\n```\n\nUsing `INT_MAX` means sleep forever and tasks should always sleep until they have\nreal work todo. Tasks **should not periodically poll for work** with a snooze of\nn seconds.\n\n### Utilise `wake()`\nWhen a task has work todo, some other function should be waking the task using the wake method.\n\n```\nExecutorPool::get()-\u003ewake(myNewTaskId)`\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcouchbase%2Fep-engine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcouchbase%2Fep-engine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcouchbase%2Fep-engine/lists"}