1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2016 Couchbase, Inc
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use this file except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  */
17 
18 /*
19  * Engine manager provides methods for creating and deleting of engine
20  * handles/structs and the creation and safe teardown of the scrubber thread.
21  *
22  *  Note: A single scrubber exists for the purposes of running a user requested
23  *  scrub and for background deletion of bucket items when a bucket is
24  *  destroyed.
25  */
26 
27 #include "engine_manager.h"
28 #include "default_engine_internal.h"
29 
30 #include <chrono>
31 #include <memory>
32 
33 static std::unique_ptr<EngineManager> engineManager;
34 
EngineManager()35 EngineManager::EngineManager()
36   : scrubberTask(*this),
37     shuttingdown(false) {}
38 
~EngineManager()39 EngineManager::~EngineManager() {
40     shutdown();
41 }
42 
createEngine()43 struct default_engine* EngineManager::createEngine() {
44     std::lock_guard<std::mutex> lck(lock);
45     if (shuttingdown) {
46         return nullptr;
47     }
48 
49     try {
50         static bucket_id_t bucket_id;
51 
52         struct default_engine* newEngine = new struct default_engine();
53         if (bucket_id + 1 == 0) {
54             // We've used all of the available id's
55             delete newEngine;
56             return nullptr;
57         }
58         default_engine_constructor(newEngine, bucket_id++);
59         engines.insert(newEngine);
60 
61         return newEngine;
62     } catch (const std::bad_alloc&) {
63         return nullptr;
64     }
65 }
66 
requestDestroyEngine(struct default_engine* engine)67 void EngineManager::requestDestroyEngine(struct default_engine* engine) {
68     std::lock_guard<std::mutex> lck(lock);
69     if (!shuttingdown) {
70         scrubberTask.placeOnWorkQueue(engine, true);
71     }
72 }
73 
scrubEngine(struct default_engine* engine)74 void EngineManager::scrubEngine(struct default_engine* engine) {
75     std::lock_guard<std::mutex> lck(lock);
76     if (!shuttingdown) {
77         scrubberTask.placeOnWorkQueue(engine, false);
78     }
79 }
80 
waitForScrubberToBeIdle(std::unique_lock<std::mutex>& lck)81 void EngineManager::waitForScrubberToBeIdle(std::unique_lock<std::mutex>& lck) {
82     if (!lck.owns_lock()) {
83         throw std::logic_error("EngineManager::waitForScrubberToBeIdle: Lock must be held");
84     }
85 
86     while (!scrubberTask.isIdle()) {
87         auto& task = scrubberTask;
88         // There is a race for the isIdle call, and I don't want to solve it
89         // by using a mutex as that would result in the use of trying to
90         // acquire multiple locks (which is a highway to deadlocks ;-)
91         //
92         // The scrubber does *not* hold the for the scrubber while calling
93         // notify on this condition variable.. And the state is then Scrubbing
94         // That means that this thread will wake, grab the mutex and check
95         // the state which is still Scrubbing and go back to sleep (depending
96         // on the scheduling order)..
97         cond.wait_for(lck,
98                       std::chrono::milliseconds(10),
99                       [&task] {
100             return task.isIdle();
101         });
102     }
103 }
104 
105 /*
106  * Join the scrubber and delete any data which wasn't cleaned by clients
107  */
shutdown()108 void EngineManager::shutdown() {
109     std::unique_lock<std::mutex> lck(lock);
110     if (!shuttingdown) {
111         shuttingdown = true;
112 
113         // Wait until the scrubber is done with all of its tasks
114         waitForScrubberToBeIdle(lck);
115 
116         // Do we have any engines defined?
117         if (!engines.empty()) {
118             // Tell it to go ahead and scrub all engines
119             for (auto engine : engines) {
120                 scrubberTask.placeOnWorkQueue(engine, true);
121             }
122 
123             // Wait for all of the engines to be deleted
124             auto& set = engines;
125             // Ideally we should use cond.wait() here, but I _HAVE_ seen
126             // this wait stuck on our commit validator
127             // builders on.. tata WINDOWS
128             // Given that it means that we need to log into the commit
129             // validator builders to manually kill the process in order for
130             // other builds to succeed we'll just timeout and recheck every
131             // once and a while until we figure out why we might miss a
132             // notification signal.
133             cond.wait_for(lck, std::chrono::milliseconds(100), [&set] {
134                 return set.empty();
135             });
136 
137             // wait for the scrubber to become idle again
138             waitForScrubberToBeIdle(lck);
139         }
140 
141         scrubberTask.shutdown();
142         scrubberTask.joinThread();
143     }
144 }
145 
notifyScrubComplete(struct default_engine* engine, bool destroy)146 void EngineManager::notifyScrubComplete(struct default_engine* engine,
147                                         bool destroy) {
148     if (destroy) {
149         destroy_engine_instance(engine);
150     }
151 
152     std::lock_guard<std::mutex> lck(lock);
153     if (destroy) {
154         engines.erase(engine);
155         delete engine;
156     }
157 
158     cond.notify_one();
159 }
160 
getEngineManager()161 EngineManager& getEngineManager() {
162     static std::mutex createLock;
163     if (engineManager.get() == nullptr) {
164         std::lock_guard<std::mutex> lg(createLock);
165         if (engineManager.get() == nullptr) {
166             engineManager.reset(new EngineManager());
167         }
168     }
169     return *engineManager.get();
170 }
171 
172 // C API methods follow.
engine_manager_create_engine()173 struct default_engine* engine_manager_create_engine() {
174     return getEngineManager().createEngine();
175 }
176 
engine_manager_delete_engine(struct default_engine* engine)177 void engine_manager_delete_engine(struct default_engine* engine) {
178     getEngineManager().requestDestroyEngine(engine);
179 }
180 
engine_manager_scrub_engine(struct default_engine* engine)181 void engine_manager_scrub_engine(struct default_engine* engine) {
182     getEngineManager().scrubEngine(engine);
183 }
184 
engine_manager_shutdown()185 void engine_manager_shutdown() {
186     // will block waiting for scrubber to finish
187     // Note that it would be tempting to just call reset on the unique_ptr,
188     // but then we could recreate the object on accident by calling
189     // one of the other functions which in turn call getEngineManager()
190     getEngineManager().shutdown();
191 }
192