/
ContextMap.h
197 lines (170 loc) · 6.34 KB
/
ContextMap.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved.
#pragma once
#include "autowiring_error.h"
#include "CoreContext.h"
#include <map>
extern std::shared_ptr<CoreContext> NewContextThunk(void);
/// <summary>
/// A context map allows the management of semitransient contexts
/// </summary>
/// <remarks>
/// Context members are allowed to create and manage their own subcontexts at will. In order
/// to ensure that the map remains accurate, however, it's necessary to evict any contexts
/// that exit, and it's also necessary to avoid allowing natural cleanup with a context's
/// reference count goes to zero.
///
/// The context map solves this by maintaining weak pointers to the contexts it knows about,
/// and registers listeners that wait for context termination and clean up the map accordingly.
///
/// All of the context map members are thread-safe.
/// </remarks>
template<class Key>
class ContextMap
{
public:
struct tracker:
public std::mutex
{
tracker(void):
destroyed(false)
{}
// True when the enclosing context map has been destroyed
bool destroyed;
};
ContextMap(void):
m_tracker(std::make_shared<tracker>())
{
}
~ContextMap(void) {
// Teardown pathway assurance:
std::lock_guard<std::mutex> lk(*m_tracker);
m_tracker->destroyed = true;
}
private:
// Tracker lock, used to protect against accidental destructor-contending access while still allowing
// the parent ContextMap structure to be stack-allocated
const std::shared_ptr<tracker> m_tracker;
typedef std::map<Key, std::weak_ptr<CoreContext>> t_mpType;
t_mpType m_contexts;
public:
// Accessor methods:
size_t size(void) const {return m_contexts.size();}
class iterator {
public:
iterator(ContextMap& parent) :
parent(parent)
{
std::lock_guard<std::mutex> lk(*parent.m_tracker);
// Advance to the first iterator we can actually lock down:
iter = parent.m_contexts.begin();
while (
iter != parent.m_contexts.end() &&
!(ctxt = iter->second.lock())
)
// Failure, next entry
iter++;
}
iterator(ContextMap& parent, typename t_mpType::iterator iter, std::shared_ptr<CoreContext> ctxt) :
parent(parent),
iter(iter),
ctxt(ctxt)
{}
private:
ContextMap& parent;
typename t_mpType::iterator iter;
std::shared_ptr<CoreContext> ctxt;
public:
const iterator& operator++(void) {
// We need to ensure our local shared pointer doesn't go away until after we have
// advanced to the next entry
std::shared_ptr<CoreContext> deferred = std::move(ctxt);
// Loop until we get another stable entry:
std::lock_guard<std::mutex> lk(*parent.m_tracker);
do iter++;
while (iter != parent.m_contexts.end() && !(ctxt = iter->second.lock()));
// We can safely unlock because the current entry won't be evicted automatically as long as
// the shared pointer reference is held down.
return *this;
}
const std::shared_ptr<CoreContext>& operator*(void) const { return ctxt; }
const CoreContext& operator->(void) const { return *ctxt; }
bool operator==(const iterator& rhs) const { return &parent == &rhs.parent && iter == rhs.iter; }
bool operator!=(const iterator& rhs) const { return !(*this == rhs); }
explicit operator bool(void) const {
return ctxt;
}
};
iterator begin(void) { return iterator(*this); }
iterator end(void) { return iterator(*this, m_contexts.end(), nullptr); }
template<class Fn>
void Enumerate(Fn&& fn) {
std::lock_guard<std::mutex> lk(*m_tracker);
for(const auto& entry : m_contexts) {
auto ctxt = entry.second.lock();
if(ctxt && !fn(entry.first, ctxt))
return;
}
}
/// <summary>
/// Adds a new context to the map
/// </summary>
/// <remarks>
/// The context will be tracked until its reference count hits zero. This method does not
/// alter the reference count of the passed context.
///
/// An exception will be thrown if the passed key is already associated with a context
/// </remarks>
void Add(const Key& key, std::shared_ptr<CoreContext>& context) {
std::lock_guard<std::mutex> lk(*m_tracker);
auto& rhs = m_contexts[key];
if(!rhs.expired())
throw autowiring_error("Specified key is already associated with another context");
rhs = context;
std::weak_ptr<tracker> tracker(m_tracker);
context->AddTeardownListener([this, key, tracker] {
// Prevent the map from being deleted while we process this teardown notice:
auto locked = tracker.lock();
if(!locked)
// Context survived the map
return;
std::lock_guard<std::mutex> lk(*locked);
if(locked->destroyed)
// Map passed through teardown pathway while we were trying to lock it
return;
// We only remove the key if it's expired. Under normal circumstances, the key will
// be expired by the time we get here, but there is a small chance that the same key
// will be introduced at the exact time that the context is tearing down, which could
// cause the slot for that key to be reclaimed earlier than expected.
auto q = m_contexts.find(key);
// There is a very unlikely race which could cause the key to not be found. It involves
// a second context being created and destroyed on the same key as some original context.
// This causes one of the two context's teardown handlers to attempt to evict the same
// key at the same time, and one of them will of course fail.
if(q == m_contexts.end())
return;
// Try to lock--potentially, this key has been reclaimed by a different context, and in
// that case, the new context will gain the responsibility of tearing down this key when
// the time comes.
auto sp = q->second.lock();
if(!sp)
this->m_contexts.erase(key);
});
}
/// <summary>
/// Attempts to find a context by the specified key
/// </summary>
std::shared_ptr<CoreContext> Find(const Key& key) const {
std::lock_guard<std::mutex> lk(*m_tracker);
auto q = m_contexts.find(key);
return
q == m_contexts.end() ?
std::shared_ptr<CoreContext>() :
q->second.lock();
}
/// <summary>
/// Identical to Find
/// </summary>
std::shared_ptr<CoreContext> operator[](const Key& key) const {
return Find(key);
}
};