MB-16181: Filters with deleted collections
[ep-engine.git] / README.md
1 # Eventually Persistent Engine
2 ## Threads
3 Code in ep-engine is executing in a multithreaded environment, two classes of
4 thread exist.
5
6 1. memcached's threads, for servicing a client and calling in via the
7 [engine API] (https://github.com/couchbase/memcached/blob/master/include/memcached/engine.h)
8 2. ep-engine's threads, for running tasks such as the document expiry pager
9 (see subclasses of `GlobalTasks`).
10
11 ## Synchronisation Primitives
12
13 There are two mutual-exclusion primitives available in ep-engine (in
14 addition to those provided by the C++ standard library):
15
16 1. `RWLock` shared, reader/writer lock - [rwlock.h](./src/rwlock.h)
17 2. `SpinLock` 1-byte exclusive lock - [atomix.h](./src/atomic.h)
18
19 A condition-variable is also available called `SyncObject`
20 [syncobject.h](./src/syncobject.h). `SyncObject` glues a `std::mutex` and
21 `std::condition_variable` together in one object.
22
23 These primitives are managed via RAII wrappers - [locks.h](./src/locks.h).
24
25 1. `LockHolder` - a deprecated alias for std::lock_guard
26 2. `MultiLockHolder` - for acquiring an array of `std::mutex` or `SyncObject`.
27
28 ### Mutex
29 The general style is to create a `std::lock_guard` when you need to acquire a
30 `std::mutex`, the constructor will acquire and when the `lock_guard` goes out of
31 scope, the destructor will release the `std::mutex`. For certain use-cases the
32 caller can explicitly lock/unlock a `std::mutex` via the `std::unique_lock`
33 class.
34
35 ```c++
36 std::mutex mutex;
37 void example1() {
38     std::lock_guard<std::mutex> lockHolder(mutex);
39     ...
40     return;
41 }
42
43 void example2() {
44     std::unique_lock<std::mutex> lockHolder(mutex);
45     ...
46     lockHolder.unlock();
47     ...
48     lockHolder.lock();
49     ...
50     return;
51 }
52 ```
53
54 A `MultiLockHolder` allows an array of locks to be conveniently acquired and
55 released, and similarly to `LockHolder` the caller can choose to manually
56 lock/unlock at any time (with all locks locked/unlocked via one call).
57
58 ```c++
59 std::mutex mutexes[10];
60 Object objects[10];
61 void foo() {
62     MultiLockHolder lockHolder(&mutexes, 10);
63     for (int ii = 0; ii < 10; ii++) {
64         objects[ii].doStuff();
65     }
66     return;
67 }
68 ```
69
70 ### RWLock
71
72 `RWLock` allows many readers to acquire it and exclusive access for a writer.
73 Like a std::mutex `RWLock` can be used with a std::lock_guard. The RWLock can
74 either be explicitly casted to a `ReaderLock` / `WriterLock` through its
75 `reader()` and `writer()` member functions or you can rely on the implicit
76 conversions used by the `lock_guard` constructor.
77
78 ```c++
79 RWLock rwLock;
80 Object thing;
81
82 void foo1() {
83     std::lock_guard<ReaderLock> rlh(rwLock);
84     if (thing.getData()) {
85     ...
86     }
87 }
88
89 void foo2() {
90     std::lock_guard<WriterLock> wlh(rwLock);
91     thing.setData(...);
92 }
93 ```
94
95 ### SyncObject
96
97 `SyncObject` inherits from `std::mutex` and is thus managed via a `LockHolder` or
98 `MultiLockHolder`. The `SyncObject` provides the conditional-variable
99 synchronisation primitive enabling threads to block and be woken.
100
101 The wait/wakeOne/wake method is provided by the `SyncObject`.
102
103 Note that `wake` will wake up a single blocking thread, `wakeOne` will wake up
104 every thread that is blocking on the `SyncObject`.
105
106 ```c++
107 SyncObject syncObject;
108 bool sleeping = false;
109 void foo1() {
110     LockHolder lockHolder(&syncObject);
111     sleeping = true;
112     syncObject.wait(); // the mutex is released and the thread put to sleep
113     // when wait returns the mutex is reacquired
114     sleeping = false;
115 }
116
117 void foo2() {
118     LockHolder lockHolder(&syncObject);
119     if (sleeping) {
120         syncObject.notifyOne();
121     }
122 }
123 ```
124
125 ### SpinLock
126
127 A `SpinLock` uses a single byte for the lock and our own code to spin until the
128 lock is acquired. The intention for this lock is for low contention locks.
129
130 The RAII pattern is just like for a mutex.
131
132
133 ```c++
134 SpinLock spinLock;
135 void example1() {
136     std::lock_guard<SpinLock> lockHolder(&spinLock);
137     ...
138     return;
139 }
140 ```
141
142 ### _UNLOCKED convention
143
144 ep-engine has a function naming convention that indicates the function should
145 be called with a lock acquired.
146
147 For example the following `doStuff_UNLOCKED` method indicates that it expect a
148 lock to be held before the function is called. What lock should be acquired
149 before calling is not defined by the convention.
150
151 ```c++
152 void Object::doStuff_UNLOCKED() {
153 }
154
155 void Object::run() {
156     LockHolder lockHolder(&mutex);
157     doStuff_UNLOCKED();
158     return;
159 }
160 ```
161
162 ## Atomic / thread-safe data structures
163
164 In addition to the basic synchronization primitives described above,
165 there are also the following higher-level data structures which
166 support atomic / thread-safe access from multiple threads:
167
168 1. `AtomicQueue`: thread-safe, approximate-FIFO queue, optimized for
169    multiple-writers, one reader - [atomicqueue.h](./src/atomicqueue.h)
170 2. `AtomicUnorderedMap` : thread-safe unordered map -
171    [atomic_unordered_map.h](./src/atomic_unordered_map.h)
172
173 ## Thread Local Storage (ObjectRegistry).
174
175 Threads in ep-engine are servicing buckets and when a thread is dispatched to
176 serve a bucket, the pointer to the `EventuallyPersistentEngine` representing
177 the bucket is placed into thread local storage, this avoids the need for the
178 pointer to be passed along the chain of execution as a formal parameter.
179
180 Both threads servicing frontend operations (memcached's threads) and ep-engine's
181 own task threads will save the bucket's engine pointer before calling down into
182 engine code.
183
184 Calling `ObjectRegistry::onSwitchThread(enginePtr)` will save the `enginePtr`
185 in thread-local-storage so that subsequent task code can retrieve the pointer
186 with `ObjectRegistry::getCurrentEngine()`.
187
188 ## Tasks
189
190 A task is created by creating a sub-class (the `run()` method is the entry point
191 of the task) of the `GlobalTask` class and it is scheduled onto one of 4 task
192 queue types. Each task should be declared in `src/tasks.defs.h` using the TASK
193 macro. Using this macro ensures correct generation of a task-type ID, priority,
194 task name and ultimately ensures each task gets its own scheduling statistics.
195
196 The recipe is simple.
197
198 ### Add your task's class name with its priority into `src/tasks.defs.h`
199  * A lower value priority is 'higher'.
200 ```
201 TASK(MyNewTask, 1) // MyNewTask has priority 1.
202 ```
203
204 ### Create your class and set its ID using `MY_TASK_ID`.
205
206 ```
207 class MyNewTask : public GlobalTask {
208 public:
209     MyNewTask(EventuallyPersistentEngine* e)
210         : GlobalTask(e/*engine/,
211                      MY_TASK_ID(MyNewTask),
212                      0.0/*snooze*/){}
213 ...
214 ```
215
216 ### Define pure-virtual methods in `MyNewTask`
217 * run method
218
219 The run method is invoked when the task is executed. The method should return
220 true if it should be scheduled again. If false is returned, the instance of the
221 task is never re-scheduled and will deleted once all references to the instance are
222 gone.
223
224 ```
225 bool run() {
226    // Task code here
227    return schedule again?;
228 }
229 ```
230
231 * Define the `getDescription` method to aid debugging and statistics.
232 ```
233 std::string getDescription() {
234     return "A brief description of what MyNewTask does";
235 }
236 ```
237
238 ### Schedule your task to the desired queue.
239 ```
240 ExTask myNewTask = new MyNewTask(&engine);
241 myNewTaskId = ExecutorPool::get()->schedule(myNewTask, NONIO_TASK_IDX);
242 ```
243
244 The 4 task queue types are:
245 * Readers -  `READER_TASK_IDX`
246  * Tasks that should primarily only read from 'disk'. They generally read from
247 the vbucket database files, for example background fetch of a non-resident document.
248 * Writers (they are allowed to read too) `WRITER_TASK_IDX`
249  * Tasks that should primarily only write to 'disk'. They generally write to
250 the vbucket database files, for example when flushing the write queue.
251 * Auxilliary IO `AUXIO_TASK_IDX`
252  * Tasks that read and write 'disk', but not necessarily the vbucket data files.
253 * Non IO `NONIO_TASK_IDX`
254  * Tasks that do not perform 'disk' I/O.
255
256 ### Utilise `snooze`
257
258 The snooze value of the task sets when the task should be executed. The initial snooze
259 value is set when constructing `GlobalTask`. A value of 0.0 means attempt to execute
260 the task as soon as scheduled and 5.0 would be 5 seconds from being scheduled
261 (scheduled meaning when `ExecutorPool::get()->schedule(...)` is called).
262
263 The `run()` function can also call `snooze(double snoozeAmount)` to set how long
264 before the task is rescheduled.
265
266 It is **best practice** for most tasks to actually do a sleep forever from their run function:
267
268 ```
269   snooze(INT_MAX);
270 ```
271
272 Using `INT_MAX` means sleep forever and tasks should always sleep until they have
273 real work todo. Tasks **should not periodically poll for work** with a snooze of
274 n seconds.
275
276 ### Utilise `wake()`
277 When a task has work todo, some other function should be waking the task using the wake method.
278
279 ```
280 ExecutorPool::get()->wake(myNewTaskId)`
281 ```