|
29 | 29 | // compile time. Extensions that need to drop to the raw ABI for these |
30 | 30 | // hooks would have to skip the vsql builder entirely. |
31 | 31 | // |
32 | | -// State lifetime: |
33 | | -// emplace_state<T>(args...) — SDK-owned. The SDK installs a deleter via |
34 | | -// vef_prerun_result_t::user_data_deleter and |
35 | | -// the server calls it after the user's |
36 | | -// postrun. No leak even if the extension |
37 | | -// does not write a postrun. |
38 | | -// set_user_data(void *) — caller-owned. The SDK does not install a |
39 | | -// deleter; the extension is responsible for |
40 | | -// freeing in its own postrun. |
| 32 | +// State lifetime is explicit: if prerun stores a pointer via set_user_data, |
| 33 | +// postrun is responsible for freeing it. PostrunArgs::delete_state<T>() is |
| 34 | +// the typed convenience for the common case of `new T{}` + `delete`. |
41 | 35 | // |
42 | 36 | // vef_postrun_result_t is currently empty in the ABI, so there is no |
43 | 37 | // PostrunResult wrapper. If/when the result struct gains fields, a typed |
44 | 38 | // wrapper class will be added and the postrun signature will become |
45 | 39 | // `void(PostrunArgs, PostrunResult)`. |
46 | 40 | // |
47 | | -// TODO(villagesql-beta): add a `.state<T>()` builder method on FuncBuilder |
48 | | -// that records the State type at the template level, auto-installs the |
49 | | -// prerun (default-constructed T) and routes typed prerun/postrun/VDF |
50 | | -// signatures of the form `void(T&, ...)`. Once this lands, the explicit |
51 | | -// emplace_state<T>/state<T>/delete_state<T> pattern becomes optional sugar. |
| 41 | +// TODO(villagesql-beta): add a typed-state mechanism (working name |
| 42 | +// `.state<T>()` on FuncBuilder) so extensions can declare a per-statement |
| 43 | +// state type and have the SDK manage its lifetime automatically — no |
| 44 | +// matched prerun/postrun pair required just to free state. The shape is |
| 45 | +// blocked on adding an ABI side channel (e.g. a user_data_deleter slot on |
| 46 | +// vef_prerun_result_t) so the SDK can register the destructor without |
| 47 | +// reserving the user_data pointer slot for SDK use. |
52 | 48 |
|
53 | 49 | #include <cstddef> |
54 | 50 | #include <cstring> |
55 | 51 | #include <optional> |
56 | 52 | #include <string_view> |
57 | | -#include <type_traits> |
58 | 53 | #include <utility> |
59 | 54 |
|
60 | 55 | #include <villagesql/abi/types.h> |
61 | 56 |
|
62 | 57 | namespace vsql { |
63 | 58 |
|
64 | | -namespace detail { |
65 | | - |
66 | | -// Type-erased deleter installed by PrerunResult::emplace_state<T>. Stored in |
67 | | -// vef_prerun_result_t::user_data_deleter so the server can free the |
68 | | -// SDK-owned state without knowing T. |
69 | | -template <typename T> |
70 | | -inline void typed_delete(void *p) { |
71 | | - delete static_cast<T *>(p); |
72 | | -} |
73 | | - |
74 | | -} // namespace detail |
75 | | - |
76 | 59 | // ============================================================================= |
77 | 60 | // Prerun |
78 | 61 | // ============================================================================= |
@@ -129,37 +112,10 @@ class PrerunResult { |
129 | 112 | public: |
130 | 113 | explicit PrerunResult(vef_prerun_result_t *r) : r_(r) {} |
131 | 114 |
|
132 | | - // Heap-allocate a T as the per-statement state, accessible from the VDF |
133 | | - // and postrun via PostrunArgs::state<T>() (or as a `T&` first param on the |
134 | | - // VDF/postrun). The SDK installs a deleter via the ABI's |
135 | | - // user_data_deleter slot, so the server frees the T automatically after |
136 | | - // postrun — no postrun is required just to clean up state. |
137 | | - // |
138 | | - // Allocator boundary: both the `new T` here and the matching `delete` in |
139 | | - // the SDK-installed deleter run inside the extension's .so via the |
140 | | - // extension's allocator. The server only invokes the deleter through a |
141 | | - // function pointer; it never touches extension-heap memory directly. So |
142 | | - // it is safe for the extension to link a different malloc/free than |
143 | | - // mysqld. |
144 | | - // |
145 | | - // For non-C++ allocations (malloc/arena/pool) or polymorphic state, use |
146 | | - // set_user_data instead and own the lifetime yourself. |
147 | | - template <typename T, typename... Args> |
148 | | - T *emplace_state(Args &&...args) { |
149 | | - static_assert(!std::is_pointer_v<T>, |
150 | | - "emplace_state<T> allocates a T on the heap. To store an " |
151 | | - "existing pointer in user_data, call set_user_data(p) " |
152 | | - "instead."); |
153 | | - auto *p = new T(std::forward<Args>(args)...); |
154 | | - r_->user_data = p; |
155 | | - r_->user_data_deleter = &detail::typed_delete<T>; |
156 | | - return p; |
157 | | - } |
158 | | - |
159 | | - // Stash an arbitrary void* the extension owns. Use this for non-C++ |
160 | | - // allocations (malloc/arena/pool), polymorphic state, or anything that |
161 | | - // doesn't fit the typed emplace_state pattern. Postrun reads back via |
162 | | - // PostrunArgs::user_data(). |
| 115 | + // Stash a pointer the extension owns. The VDF and postrun read it back |
| 116 | + // via PostrunArgs::user_data() or PostrunArgs::state<T>(). The SDK does |
| 117 | + // not free anything stashed here; pair this with a postrun that calls |
| 118 | + // PostrunArgs::delete_state<T>() (or free()) on the same pointer. |
163 | 119 | void set_user_data(void *p) { r_->user_data = p; } |
164 | 120 |
|
165 | 121 | // Request that the per-row result buffer be at least n bytes. The server |
@@ -188,22 +144,20 @@ class PostrunArgs { |
188 | 144 | public: |
189 | 145 | explicit PostrunArgs(const vef_postrun_args_t *a) : a_(a) {} |
190 | 146 |
|
191 | | - // Read the raw user_data slot. Use this when prerun set it via |
192 | | - // set_user_data (raw / non-C++ allocation). |
| 147 | + // Read the raw user_data slot — whatever prerun stored via set_user_data. |
| 148 | + // Use this for non-C++ allocations (malloc/arena/pool) or anything that |
| 149 | + // doesn't fit a single typed T. |
193 | 150 | void *user_data() const { return a_->user_data; } |
194 | 151 |
|
195 | | - // Convenience: cast user_data to a T*. Use this when prerun set it via |
196 | | - // emplace_state<T>(). |
| 152 | + // Convenience: static_cast user_data to a T*. Pair with prerun's |
| 153 | + // set_user_data(new T{...}). |
197 | 154 | template <typename T> |
198 | 155 | T *state() const { |
199 | 156 | return static_cast<T *>(a_->user_data); |
200 | 157 | } |
201 | 158 |
|
202 | | - // Convenience: `delete state<T>()` in one call. Use this only when prerun |
203 | | - // called set_user_data with a heap-allocated T and you want a typed free. |
204 | | - // Do NOT call this if prerun used emplace_state<T> — the SDK already |
205 | | - // frees the state via the installed deleter, and calling delete_state |
206 | | - // would be a double-free. |
| 159 | + // Convenience: `delete state<T>()` in one call. Pair with prerun's |
| 160 | + // set_user_data(new T{...}). |
207 | 161 | template <typename T> |
208 | 162 | void delete_state() const { |
209 | 163 | delete state<T>(); |
|
0 commit comments