1/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 *     Copyright 2017 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/** \file
18 * This file is internal to the inner workings of
19 * Phosphor and is not intended for public consumption.
20 */
21
22#pragma once
23
24#include <atomic>
25
26#include "platform/core.h"
27
28namespace phosphor {
29
30/**
31 * Tag type for selecting non trivial constructor of ChunkLock and ChunkTenant
32 */
33struct non_trivial_constructor_t {
34};
35
36constexpr non_trivial_constructor_t non_trivial_constructor {};
37
38class SlaveChunkLock;
39class MasterChunkLock;
40class TraceChunk;
41
42/**
43 * ChunkLock is used to encapsulate the logic behind locking of a
44 * TraceLog::ChunkTenant. It is used to safely handle transitions between
45 * various states that a ChunkTenant might be in.
46 *
47 * ChunkLock is conceptually the merging of two spin-locks, there are 3 states:
48 *
49 * - Unlocked
50 * - SlaveLocked, locked by the TraceLog::logEvent frontend
51 * - MasterLocked, locked by the TraceLog::evictThreads backend
52 *
53 * There are 4 transitions between states:
54 *
55 * - slaveLock [Unlocked->SlaveLocked]
56 * - slaveUnlock [SlaveLocked->Unlocked]
57 * - masterLock [Unlocked->MasterLocked]
58 * - masterUnlock [MasterLocked->Unlocked]
59 *
60 * This is somewhat similar to a Reader/Writer lock with only one reader
61 */
62class PHOSPHOR_API ChunkLock {
63public:
64
65    /**
66     * ChunkLock is trivially constructible to allow for use in
67     * MacOS compatible `thread_local` variables.
68     *
69     * ChunkLock is 'constructed' by memsetting to zero
70     */
71    ChunkLock() = default;
72
73    /**
74     * Non-trivial constructor for regular use
75     */
76    ChunkLock(non_trivial_constructor_t t);
77
78    /*
79     * Explicit copy-constructor / copy-assignment
80     * as they are deleted by std::atomic
81     */
82    ChunkLock(const ChunkLock& other);
83    ChunkLock& operator=(const ChunkLock& other);
84
85    /**
86     * This method will acquire the slave lock.
87     *
88     * This specific method typically shouldn't be used and instead
89     * slaveMaybeLock() should be used as the end-user usually won't want
90     * to block while the master lock is held. This method is included
91     * to allow for a complete `Lockable` concept over the slave lock.
92     */
93    void lockSlave();
94
95    /**
96     * This method will attempt to acquire the slave lock without blocking
97     * if the master lock is currently held.
98     *
99     * @return true if slave lock is acquired, false if the acquiring the
100     *         slave lock would block waiting for the master lock
101     */
102    bool tryLockSlave();
103
104    /**
105     * This method will release the slave lock
106     *
107     * When not built as debug this will behave identically to unlockMaster()
108     * as it will not verify the starting state for performance reasons.
109     */
110    void unlockSlave();
111
112    /**
113     * @return A SlaveChunkLock reference to this object's slave lock
114     */
115    SlaveChunkLock& slave();
116
117    /**
118     * This method will acquire the master lock
119     */
120    void lockMaster();
121
122    /**
123     * This method will release the master lock
124     *
125     * When not built as debug this will behave identically to unlockSlave()
126     * as it will not verify the starting state for performance reasons.
127     */
128    void unlockMaster();
129
130    /**
131     * @return A MasterChunkLock reference to this object's master lock
132     */
133    MasterChunkLock& master();
134
135protected:
136    enum class State {
137        // State is explicitly defined as 0 to allow for zero-initialization
138        Unlocked = 0x00,
139        SlaveLocked,
140        MasterLocked
141    };
142
143    std::atomic<State> state;
144
145    // Increase size to at least that of a cacheline
146    char cacheline_pad[64 - sizeof(std::atomic<State>)];
147};
148
149/**
150 * Lockable concept implementation around a ChunkLock's slave lock
151 */
152class PHOSPHOR_API SlaveChunkLock : public ChunkLock {
153public:
154    void lock();
155    bool try_lock();
156    void unlock();
157};
158
159/**
160 * BasicLockable concept implementation around a ChunkLock's master lock
161 */
162class PHOSPHOR_API MasterChunkLock : public ChunkLock {
163public:
164    void lock();
165    void unlock();
166};
167
168}