ledger-core
proxy_cache_interface.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 <memory>
20 #include <functional>
21 #include <typeindex>
22 
23 namespace djinni {
24 
25 /*
26  * The template parameters we receive here can be a number of different types: C++ smart
27  * pointers, custom wrappers, or language-specific types (like ObjC's `id` / `__weak id`).
28  * If custom wrapper types are used, like the `JavaWeakRef` type in the JNI library, then
29  * they must implement `.get()` or `.lock()` by analogy with C++'s smart pointers.
30  *
31  * We assume that built-in types are pointer-compatible. This is the case with ObjC: for
32  * example, __weak id pointers can be implicitly converted to __strong id, and so on.
33  *
34  * (The helper for .lock() is only used by proxy_cache_impl.hpp, so it's defined there.)
35  */
36 template <typename T> static inline auto get_unowning(const T & ptr) -> decltype(ptr.get()) {
37  return ptr.get();
38 }
39 template <typename T> static inline T * get_unowning(T * ptr) { return ptr; }
40 
41 /*
42  * ProxyCache provides a mechanism for re-using proxy objects generated in one language
43  * that wrap around implementations in a different language. This is for correctness, not
44  * just performance: when we pass the same object across a language boundary twice, we want
45  * to get the same proxy object on the other side each time, so that identity semantics
46  * behave as expected.
47  *
48  * ProxyCache is instantiated with a Traits class that must contain the following typedefs.
49  * Examples refer to the use of ProxyCache to cache C++ wrappers around ObjC objects.
50  * ProxyCache itself is generic (type-erased), though Handle is not, so we use e.g. `id`
51  * and `shared_ptr<void>` rather than any specific types.
52  *
53  * - UnowningImplPointer:
54  * a non-owning pointer to an object being wrapped, e.g. __unsafe_unretained id
55  * - OwningImplPointer:
56  * a strong owning pointer to an object being wrapped, e.g. __strong id
57  * - OwningProxyPointer:
58  * a strong owning pointer to a wrapper, e.g. std::shared_ptr<void>
59  * - WeakProxyPointer:
60  * a safe weak pointer to a wrapper, e.g. std::weak_ptr<void>
61  * - UnowningImplPointerHash:
62  * a hasher for UnowningImplPointer, usually std::hash<UnowningImplPointer>, unless
63  * std::hash doesn't work with UnowningImplPointer in which case a custom type can be
64  * provided.
65  * - UnowningImplPointerEqual:
66  * an equality predicate for UnowningImplPointer, like std::equal_to<UnowningImplPointer>.
67  * In some cases (e.g. Java) a custom equality predicate may be needed.
68  *
69  * Generally, ProxyCache will be explicitly instantiated in one source file with C++11's
70  * `extern template` mechanism. The WeakProxyPointer, UnowningImplPointerHash, and
71  * UnowningImplPointerEqual types can be incomplete except for where the explicit
72  * instantiation is actually defined.
73  *
74  * Here's an overview of the structure:
75  *
76  * ______________ std::pair<ImplType,
77  * WeakProxyPonter | | UnowningImplPointer>
78  * - - - - - - - -| ProxyCache |- - - - - - - - - -
79  * | | | |
80  * | |______________| |
81  * | |
82  * ____v____ ______________ ______________v__________
83  * | | | | | |
84  * | (Proxy | ===> | ProxyCache:: | =====> | (Impl object providing |
85  * | object) | ^ | Handle<T> | T | actual functionality) |
86  * |_________| . |______________| ^ |_________________________|
87  * . .
88  * ( can be member, base, ) ( T is a generally a specific )
89  * ( or cross-language ) ( owning type like id<Foo>, )
90  * ( reference like jlong ) ( shared_ptr<Foo>, or GlobalRef )
91  *
92  * The cache contains a map from pair<ImplType, UnowningImplPointer>
93  * to WeakProxyPointer, allowing it to answer the question: "given this
94  * impl, do we already have a proxy in existence?"
95  *
96  * We use one map for all translated types, rather than a separate one for each type,
97  * to minimize duplication of code and make it so the unordered_map is as contained as
98  * possible.
99  */
100 template <typename Traits>
101 class ProxyCache {
102 public:
103  using UnowningImplPointer = typename Traits::UnowningImplPointer;
104  using OwningImplPointer = typename Traits::OwningImplPointer;
105  using OwningProxyPointer = typename Traits::OwningProxyPointer;
106  using WeakProxyPointer = typename Traits::WeakProxyPointer;
107  using UnowningImplPointerHash = typename Traits::UnowningImplPointerHash;
108  using UnowningImplPointerEqual = typename Traits::UnowningImplPointerEqual;
109  class Pimpl;
110 
111  /*
112  * Each proxy object must own a Handle. The Handle carries a strong reference to whatever
113  * the proxy wraps. When `ProxyCache::get()` creates a proxy, it also adds the proxy to
114  * the global proxy cache; Handle::~Handle() removes the reference from the cache.
115  *
116  * The Handle can be held by the proxy in any of a number of ways: as a C++ member or
117  * base, as an ObjC instance variable, or across an FFI boundary (a Java object might
118  * contain the address of a Handle as a `long` and delete it in the destructor.)
119  *
120  * T is generally a more-specialized version of OwningImplPointer. For example, when
121  * managing C++ proxies for ObjC objects, OwningImplPointer would be `id`, and the C++
122  * proxy class `MyInterface` which wraps `@protocol DBMyInterface` would contain a
123  * `Handle<id<DBMyInterface>>`.
124  *
125  * TagType should be the same type that was passed in to `get()` when this handle was
126  * created. Normally this is the same as T (a specialized OwningImplPointer), but in
127  * cases like Java where all object types are uniformly represented as `jobject` in C++,
128  * another type may be used.
129  */
130  template <typename T, typename TagType = T>
131  class Handle {
132  public:
133  template <typename... Args> Handle(Args &&... args)
134  : m_cache(get_base()), m_obj(std::forward<Args>(args)...) {}
135  Handle(const Handle &) = delete;
136  Handle & operator=(const Handle &) = delete;
137  ~Handle() { if (m_obj) cleanup(m_cache, typeid(TagType), get_unowning(m_obj)); }
138 
139  void assign(const T & obj) { m_obj = obj; }
140 
141  const T & get() const & noexcept { return m_obj; }
142 
143  private:
144  const std::shared_ptr<Pimpl> m_cache;
145  T m_obj;
146  };
147 
148  /*
149  * Function typedef for helpers passed in to allocate new objects.
150  * To reduce code size, the proxy cache type-erases the objects inside it.
151  *
152  * An allocator function takes an OwningImplPointer to the source language object,
153  * and returns a newly-created proxy.
154  *
155  * In Java, an OwningImplPointer does not provide the same identity semantics as the
156  * underlying object. (A JNI 'jobject' can be one of a few different types of reference,
157  * and JNI is structured to allow copying GCs, so an object's address might change over
158  * time.) This is why ProxyCache takes hasher and comparator paramters. In particular,
159  * the OwningImplPointer passed into the allocator might be a JNI local reference. The
160  * allocator will create a GlobalRef to the impl object and store it in the returned proxy.
161  *
162  * Because we don't constrain how Handle objects are held, there's no generic way for the
163  * proxy cache to get the GlobalRef out of the returned proxy object, so AllocatorFunction
164  * returns a pair: the first element is the newly created proxy, and the second is an
165  * UnowningImplPointer that will be used as a key in the map.
166  */
167  using AllocatorFunction =
168  std::pair<OwningProxyPointer, UnowningImplPointer>(const OwningImplPointer &);
169 
170  /*
171  * Return the existing proxy for `impl`, if any. If not, create one by calling `alloc`,
172  * store a weak reference to it in the proxy cache, and return it.
173  */
174  static OwningProxyPointer get(const std::type_index &,
175  const OwningImplPointer & impl,
176  AllocatorFunction * alloc);
177 
178 private:
179  static void cleanup(const std::shared_ptr<Pimpl> &,
180  const std::type_index &,
181  UnowningImplPointer);
182  static const std::shared_ptr<Pimpl> & get_base();
183 };
184 
185 } // namespace djinni
Definition: proxy_cache_impl.hpp:69
Definition: djinni_support.cpp:27
Definition: proxy_cache_interface.hpp:101
Definition: proxy_cache_interface.hpp:131