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
33static std::unique_ptr<EngineManager> engineManager;
34
35EngineManager::EngineManager()
36  : scrubberTask(*this),
37    shuttingdown(false) {}
38
39EngineManager::~EngineManager() {
40    shutdown();
41}
42
43struct 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
67void 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
74void 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
81void 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 */
108void 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
146void 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
161EngineManager& 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.
173struct default_engine* engine_manager_create_engine() {
174    return getEngineManager().createEngine();
175}
176
177void engine_manager_delete_engine(struct default_engine* engine) {
178    getEngineManager().requestDestroyEngine(engine);
179}
180
181void engine_manager_scrub_engine(struct default_engine* engine) {
182    getEngineManager().scrubEngine(engine);
183}
184
185void 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