ledger-core
proxy_cache_impl.hpp
1 //
2 // Copyright 2015 Dropbox, Inc.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 
17 #pragma once
18 
19 #include "proxy_cache_interface.hpp"
20 #include <functional>
21 #include <mutex>
22 #include <unordered_map>
23 
24 // """
25 // This place is not a place of honor.
26 // No highly esteemed deed is commemorated here.
27 // Nothing valued is here.
28 // This place is a message and part of a system of messages.
29 // Pay attention to it!
30 // Sending this message was important to us.
31 // We considered ourselves to be a powerful culture.
32 // """
33 //
34 // From "Expert Judgment on Markers to Deter Inadvertent Human Intrusion into the Waste
35 // Isolation Pilot Plant", Sandia National Laboratories report SAND92-1382 / UC-721, p. F-49
36 
37 namespace djinni {
38 
39 // See comment on `get_unowning()` in proxy_cache_interface.hpp.
40 template <typename T> static inline auto upgrade_weak(const T & ptr) -> decltype(ptr.lock()) {
41  return ptr.lock();
42 }
43 template <typename T> static inline T * upgrade_weak(T* ptr) { return ptr; }
44 template <typename T> static inline bool is_expired(const T & ptr) { return ptr.expired(); }
45 template <typename T> static inline bool is_expired(T* ptr) { return !ptr; }
46 
47 /*
48  * Generic proxy cache.
49  *
50  * This provides a general-purpose mechanism for proxies to be re-used. When we pass an object
51  * across the language boundary from A to B, we must create a proxy object within language B
52  * that passes calls back to language A. For example, if have a C++ object that is passed into
53  * Java, we would create a Java object that owns a `shared_ptr` and has a set of native methods
54  * that call in to C++.
55  *
56  * When we create such an object, we also want to cache a weak reference to it, so that if we
57  * later pass the *same* object across the boundary, the same proxy will be returned. This is
58  * necessary for correctness in some situations: for example, in the case of an `add_listener`
59  * and `remove_listener` pattern.
60  *
61  * To reduce code size, only one GenericProxyCache need be instantiated for each language
62  * boundary direction. The pointer types passed to this function can be generic, e.g. `id`,
63  * `shared_ptr<void>`, `jobject`, etc.
64  *
65  * In the types below, "Impl" refers to some interface that is being wrapped, and Proxy refers
66  * to the generated other-language object that wraps it.
67  */
68 template <typename Traits>
69 class ProxyCache<Traits>::Pimpl {
70  using Key = std::pair<std::type_index, UnowningImplPointer>;
71 
72 public:
73  /*
74  * Look up an object in the proxy cache, and create a new one if not found.
75  *
76  * This takes a function pointer, not an arbitrary functor, because we want to minimize
77  * code size: this function should only be instantiated *once* per langauge direction.
78  */
79  OwningProxyPointer get(const std::type_index & tag,
80  const OwningImplPointer & impl,
81  AllocatorFunction * alloc) {
82  std::unique_lock<std::mutex> lock(m_mutex);
83  UnowningImplPointer ptr = get_unowning(impl);
84  auto existing_proxy_iter = m_mapping.find({tag, ptr});
85  if (existing_proxy_iter != m_mapping.end()) {
86  OwningProxyPointer existing_proxy = upgrade_weak(existing_proxy_iter->second);
87  if (existing_proxy) {
88  return existing_proxy;
89  } else {
90  // The weak reference is expired, so prune it from the map eagerly.
91  m_mapping.erase(existing_proxy_iter);
92  }
93  }
94 
95  auto alloc_result = alloc(impl);
96  m_mapping.emplace(Key{tag, alloc_result.second}, alloc_result.first);
97  return alloc_result.first;
98  }
99 
100  /*
101  * Erase an object from the proxy cache.
102  */
103  void remove(const std::type_index & tag, const UnowningImplPointer & impl_unowning) {
104  std::unique_lock<std::mutex> lock(m_mutex);
105  auto it = m_mapping.find({tag, impl_unowning});
106  if (it != m_mapping.end()) {
107  // The entry in the map should already be expired: this is called from Handle's
108  // destructor, so the proxy must already be gone. However, remove() does not
109  // happen atomically with the proxy object becoming weakly reachable. It's
110  // possible that during the window between when the weak-ref holding this proxy
111  // expires and when we enter remove() and take m_mutex, another thread could have
112  // created a new proxy for the same original object and added it to the map. In
113  // that case, `it->second` will contain a live pointer to a different proxy object,
114  // not an expired weak pointer to the Handle currently being destructed. We only
115  // remove the map entry if its pointer is already expired.
116  if (is_expired(it->second)) {
117  m_mapping.erase(it);
118  }
119  }
120  }
121 
122 private:
123  struct KeyHash {
124  std::size_t operator()(const Key & k) const {
125  return k.first.hash_code() ^ UnowningImplPointerHash{}(k.second);
126  }
127  };
128 
129  struct KeyEqual {
130  bool operator()(const Key & lhs, const Key & rhs) const {
131  return lhs.first == rhs.first
132  && UnowningImplPointerEqual{}(lhs.second, rhs.second);
133  }
134  };
135 
136  std::unordered_map<Key, WeakProxyPointer, KeyHash, KeyEqual> m_mapping;
137  std::mutex m_mutex;
138 
139  // Only ProxyCache<Traits>::get_base() can allocate these objects.
140  Pimpl() = default;
141  friend class ProxyCache<Traits>;
142 };
143 
144 template <typename Traits>
145 void ProxyCache<Traits>::cleanup(const std::shared_ptr<Pimpl> & base,
146  const std::type_index & tag,
147  UnowningImplPointer ptr) {
148  base->remove(tag, ptr);
149 }
150 
151 /*
152  * Magic-static singleton.
153  *
154  * It's possible for someone to hold Djinni-static objects in a global (like a shared_ptr
155  * at namespace scope), which can cause problems at static destruction time: if the proxy
156  * cache itself is destroyed before the other global, use-of-destroyed-object will result.
157  * To fix this, we make it possible to take a shared_ptr to the GenericProxyCache instance,
158  * so it will only be destroyed once all references are gone.
159  */
160 template <typename Traits>
161 auto ProxyCache<Traits>::get_base() -> const std::shared_ptr<Pimpl> & {
162  static const std::shared_ptr<Pimpl> instance(new Pimpl);
163  // Return by const-ref. This is safe to call any time except during static destruction.
164  // Returning by reference lets us avoid touching the refcount unless needed.
165  return instance;
166 }
167 
168 template <typename Traits>
169 auto ProxyCache<Traits>::get(const std::type_index & tag,
170  const OwningImplPointer & impl,
171  AllocatorFunction * alloc)
172  -> OwningProxyPointer {
173  return get_base()->get(tag, impl, alloc);
174 }
175 
176 } // namespace djinni
Definition: proxy_cache_impl.hpp:69
Definition: djinni_support.cpp:27
Definition: proxy_cache_interface.hpp:101