duckdb 1.5.6.1 → 2.0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +1 -1
- data/ext/duckdb/aggregate_function.c +40 -78
- data/ext/duckdb/appender.c +39 -126
- data/ext/duckdb/arrow_array_stream.c +10 -2
- data/ext/duckdb/connection.c +17 -54
- data/ext/duckdb/conveter.c +39 -76
- data/ext/duckdb/expression.c +1 -4
- data/ext/duckdb/function_executor.c +214 -248
- data/ext/duckdb/function_executor.h +10 -0
- data/ext/duckdb/function_vector.c +26 -0
- data/ext/duckdb/function_vector.h +18 -0
- data/ext/duckdb/logical_type.c +88 -170
- data/ext/duckdb/pending_result.c +53 -14
- data/ext/duckdb/prepared_statement.c +37 -138
- data/ext/duckdb/result.c +160 -162
- data/ext/duckdb/result.h +2 -0
- data/ext/duckdb/scalar_function.c +9 -69
- data/ext/duckdb/table_function.c +33 -82
- data/ext/duckdb/table_function_bind_info.c +2 -12
- data/ext/duckdb/util.c +53 -2
- data/ext/duckdb/value.c +46 -20
- data/ext/duckdb/value.h +1 -0
- data/lib/duckdb/appender.rb +7 -19
- data/lib/duckdb/config.rb +2 -2
- data/lib/duckdb/connection.rb +19 -17
- data/lib/duckdb/database.rb +1 -18
- data/lib/duckdb/interval.rb +2 -28
- data/lib/duckdb/pending_result.rb +7 -0
- data/lib/duckdb/prepared_statement.rb +5 -0
- data/lib/duckdb/result.rb +0 -29
- data/lib/duckdb/scalar_function_set.rb +1 -2
- data/lib/duckdb/table_description.rb +3 -3
- data/lib/duckdb/table_name_parser.rb +6 -36
- data/lib/duckdb/version.rb +1 -1
- data/lib/duckdb.rb +0 -14
- metadata +1 -1
|
@@ -7,13 +7,58 @@
|
|
|
7
7
|
*
|
|
8
8
|
* See also: FFI gem's approach in ext/ffi_c/Function.c
|
|
9
9
|
* https://github.com/ffi/ffi/blob/master/ext/ffi_c/Function.c
|
|
10
|
+
*
|
|
11
|
+
* This portable sync layer is the only place in this file that splits on
|
|
12
|
+
* platform. Everything below is written once against these wrappers.
|
|
10
13
|
*/
|
|
11
14
|
#ifdef _MSC_VER
|
|
12
15
|
#include <windows.h>
|
|
16
|
+
|
|
17
|
+
typedef CRITICAL_SECTION sync_mutex_t;
|
|
18
|
+
typedef CONDITION_VARIABLE sync_cond_t;
|
|
19
|
+
|
|
20
|
+
static void sync_mutex_init(sync_mutex_t *m) { InitializeCriticalSection(m); }
|
|
21
|
+
static void sync_mutex_destroy(sync_mutex_t *m) { DeleteCriticalSection(m); }
|
|
22
|
+
static void sync_lock(sync_mutex_t *m) { EnterCriticalSection(m); }
|
|
23
|
+
static void sync_unlock(sync_mutex_t *m) { LeaveCriticalSection(m); }
|
|
24
|
+
static void sync_cond_init(sync_cond_t *c) { InitializeConditionVariable(c); }
|
|
25
|
+
/* A CONDITION_VARIABLE holds no resources and has no destroy call. */
|
|
26
|
+
static void sync_cond_destroy(sync_cond_t *c) { (void)c; }
|
|
27
|
+
static void sync_signal(sync_cond_t *c) { WakeConditionVariable(c); }
|
|
28
|
+
static void sync_wait(sync_cond_t *c, sync_mutex_t *m) { SleepConditionVariableCS(c, m, INFINITE); }
|
|
13
29
|
#else
|
|
14
30
|
#include <pthread.h>
|
|
31
|
+
|
|
32
|
+
typedef pthread_mutex_t sync_mutex_t;
|
|
33
|
+
typedef pthread_cond_t sync_cond_t;
|
|
34
|
+
|
|
35
|
+
static void sync_mutex_init(sync_mutex_t *m) { pthread_mutex_init(m, NULL); }
|
|
36
|
+
static void sync_mutex_destroy(sync_mutex_t *m) { pthread_mutex_destroy(m); }
|
|
37
|
+
static void sync_lock(sync_mutex_t *m) { pthread_mutex_lock(m); }
|
|
38
|
+
static void sync_unlock(sync_mutex_t *m) { pthread_mutex_unlock(m); }
|
|
39
|
+
static void sync_cond_init(sync_cond_t *c) { pthread_cond_init(c, NULL); }
|
|
40
|
+
static void sync_cond_destroy(sync_cond_t *c) { pthread_cond_destroy(c); }
|
|
41
|
+
static void sync_signal(sync_cond_t *c) { pthread_cond_signal(c); }
|
|
42
|
+
static void sync_wait(sync_cond_t *c, sync_mutex_t *m) { pthread_cond_wait(c, m); }
|
|
15
43
|
#endif
|
|
16
44
|
|
|
45
|
+
/* Under m: set *flag and wake one waiter on c. */
|
|
46
|
+
static void sync_set_and_signal(sync_mutex_t *m, sync_cond_t *c, volatile int *flag) {
|
|
47
|
+
sync_lock(m);
|
|
48
|
+
*flag = 1;
|
|
49
|
+
sync_signal(c);
|
|
50
|
+
sync_unlock(m);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Under m: block on c until *flag is set. */
|
|
54
|
+
static void sync_wait_for_flag(sync_mutex_t *m, sync_cond_t *c, volatile int *flag) {
|
|
55
|
+
sync_lock(m);
|
|
56
|
+
while (!*flag) {
|
|
57
|
+
sync_wait(c, m);
|
|
58
|
+
}
|
|
59
|
+
sync_unlock(m);
|
|
60
|
+
}
|
|
61
|
+
|
|
17
62
|
/*
|
|
18
63
|
* Thread detection functions (available since Ruby 2.3).
|
|
19
64
|
* Used to determine the correct dispatch path for callbacks.
|
|
@@ -44,26 +89,19 @@ extern int ruby_native_thread_p(void);
|
|
|
44
89
|
struct callback_request {
|
|
45
90
|
rbduckdb_function_callback_t cb;
|
|
46
91
|
void *user_data;
|
|
47
|
-
int done;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
CONDITION_VARIABLE done_cond;
|
|
51
|
-
#else
|
|
52
|
-
pthread_mutex_t done_mutex;
|
|
53
|
-
pthread_cond_t done_cond;
|
|
54
|
-
#endif
|
|
92
|
+
volatile int done;
|
|
93
|
+
sync_mutex_t done_lock;
|
|
94
|
+
sync_cond_t done_cond;
|
|
55
95
|
struct callback_request *next;
|
|
56
96
|
};
|
|
57
97
|
|
|
58
|
-
/*
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
98
|
+
/*
|
|
99
|
+
* Global executor state. The lock and condvar are initialized once by
|
|
100
|
+
* rbduckdb_function_executor_ensure_started, before any worker can use them.
|
|
101
|
+
*/
|
|
102
|
+
static sync_mutex_t g_executor_lock;
|
|
103
|
+
static sync_cond_t g_executor_cond;
|
|
62
104
|
static int g_sync_initialized = 0;
|
|
63
|
-
#else
|
|
64
|
-
static pthread_mutex_t g_executor_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
65
|
-
static pthread_cond_t g_executor_cond = PTHREAD_COND_INITIALIZER;
|
|
66
|
-
#endif
|
|
67
105
|
static struct callback_request *g_request_list = NULL;
|
|
68
106
|
static VALUE g_executor_thread = Qnil;
|
|
69
107
|
static int g_executor_started = 0;
|
|
@@ -76,6 +114,14 @@ static int g_executor_started = 0;
|
|
|
76
114
|
*/
|
|
77
115
|
static VALUE g_proxy_threads = Qnil;
|
|
78
116
|
|
|
117
|
+
/* Lazy-init g_proxy_threads. Must be called with the GVL. */
|
|
118
|
+
static void proxy_threads_ensure(void) {
|
|
119
|
+
if (g_proxy_threads == Qnil) {
|
|
120
|
+
g_proxy_threads = rb_ary_new();
|
|
121
|
+
rb_global_variable(&g_proxy_threads);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
79
125
|
/*
|
|
80
126
|
* Replace embedded NUL bytes so the message can be handed to a C API.
|
|
81
127
|
* Returns str unchanged when there is nothing to replace.
|
|
@@ -125,7 +171,7 @@ VALUE rbduckdb_pending_error_message(void) {
|
|
|
125
171
|
/* Data passed to the executor wait function */
|
|
126
172
|
struct executor_wait_data {
|
|
127
173
|
struct callback_request *request;
|
|
128
|
-
int stop;
|
|
174
|
+
volatile int stop;
|
|
129
175
|
};
|
|
130
176
|
|
|
131
177
|
/* Runs without GVL: blocks on condvar waiting for a callback request */
|
|
@@ -134,27 +180,15 @@ static void *executor_wait_func(void *data) {
|
|
|
134
180
|
|
|
135
181
|
w->request = NULL;
|
|
136
182
|
|
|
137
|
-
|
|
138
|
-
EnterCriticalSection(&g_executor_lock);
|
|
139
|
-
while (!w->stop && g_request_list == NULL) {
|
|
140
|
-
SleepConditionVariableCS(&g_executor_cond, &g_executor_lock, INFINITE);
|
|
141
|
-
}
|
|
142
|
-
if (g_request_list != NULL) {
|
|
143
|
-
w->request = g_request_list;
|
|
144
|
-
g_request_list = g_request_list->next;
|
|
145
|
-
}
|
|
146
|
-
LeaveCriticalSection(&g_executor_lock);
|
|
147
|
-
#else
|
|
148
|
-
pthread_mutex_lock(&g_executor_mutex);
|
|
183
|
+
sync_lock(&g_executor_lock);
|
|
149
184
|
while (!w->stop && g_request_list == NULL) {
|
|
150
|
-
|
|
185
|
+
sync_wait(&g_executor_cond, &g_executor_lock);
|
|
151
186
|
}
|
|
152
187
|
if (g_request_list != NULL) {
|
|
153
188
|
w->request = g_request_list;
|
|
154
189
|
g_request_list = g_request_list->next;
|
|
155
190
|
}
|
|
156
|
-
|
|
157
|
-
#endif
|
|
191
|
+
sync_unlock(&g_executor_lock);
|
|
158
192
|
|
|
159
193
|
return NULL;
|
|
160
194
|
}
|
|
@@ -163,17 +197,7 @@ static void *executor_wait_func(void *data) {
|
|
|
163
197
|
static void executor_stop_func(void *data) {
|
|
164
198
|
struct executor_wait_data *w = (struct executor_wait_data *)data;
|
|
165
199
|
|
|
166
|
-
|
|
167
|
-
EnterCriticalSection(&g_executor_lock);
|
|
168
|
-
w->stop = 1;
|
|
169
|
-
WakeConditionVariable(&g_executor_cond);
|
|
170
|
-
LeaveCriticalSection(&g_executor_lock);
|
|
171
|
-
#else
|
|
172
|
-
pthread_mutex_lock(&g_executor_mutex);
|
|
173
|
-
w->stop = 1;
|
|
174
|
-
pthread_cond_signal(&g_executor_cond);
|
|
175
|
-
pthread_mutex_unlock(&g_executor_mutex);
|
|
176
|
-
#endif
|
|
200
|
+
sync_set_and_signal(&g_executor_lock, &g_executor_cond, &w->stop);
|
|
177
201
|
}
|
|
178
202
|
|
|
179
203
|
/*
|
|
@@ -200,18 +224,7 @@ static VALUE request_run_body(VALUE varg) {
|
|
|
200
224
|
static VALUE request_signal_done(VALUE varg) {
|
|
201
225
|
struct callback_request *req = (struct callback_request *)varg;
|
|
202
226
|
|
|
203
|
-
|
|
204
|
-
EnterCriticalSection(&req->done_lock);
|
|
205
|
-
req->done = 1;
|
|
206
|
-
WakeConditionVariable(&req->done_cond);
|
|
207
|
-
LeaveCriticalSection(&req->done_lock);
|
|
208
|
-
#else
|
|
209
|
-
pthread_mutex_lock(&req->done_mutex);
|
|
210
|
-
req->done = 1;
|
|
211
|
-
pthread_cond_signal(&req->done_cond);
|
|
212
|
-
pthread_mutex_unlock(&req->done_mutex);
|
|
213
|
-
#endif
|
|
214
|
-
|
|
227
|
+
sync_set_and_signal(&req->done_lock, &req->done_cond, &req->done);
|
|
215
228
|
return Qnil;
|
|
216
229
|
}
|
|
217
230
|
|
|
@@ -244,18 +257,13 @@ static VALUE executor_thread_func(void *data) {
|
|
|
244
257
|
void rbduckdb_function_executor_ensure_started(void) {
|
|
245
258
|
if (g_executor_started) return;
|
|
246
259
|
|
|
247
|
-
#ifdef _MSC_VER
|
|
248
260
|
if (!g_sync_initialized) {
|
|
249
|
-
|
|
250
|
-
|
|
261
|
+
sync_mutex_init(&g_executor_lock);
|
|
262
|
+
sync_cond_init(&g_executor_cond);
|
|
251
263
|
g_sync_initialized = 1;
|
|
252
264
|
}
|
|
253
|
-
#endif
|
|
254
265
|
|
|
255
|
-
|
|
256
|
-
g_proxy_threads = rb_ary_new();
|
|
257
|
-
rb_global_variable(&g_proxy_threads);
|
|
258
|
-
}
|
|
266
|
+
proxy_threads_ensure();
|
|
259
267
|
|
|
260
268
|
g_executor_thread = rb_thread_create(executor_thread_func, NULL);
|
|
261
269
|
rb_global_variable(&g_executor_thread);
|
|
@@ -275,46 +283,21 @@ static void dispatch_callback_to_executor(rbduckdb_function_callback_t cb, void
|
|
|
275
283
|
req.done = 0;
|
|
276
284
|
req.next = NULL;
|
|
277
285
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
InitializeConditionVariable(&req.done_cond);
|
|
281
|
-
|
|
282
|
-
/* Enqueue the request */
|
|
283
|
-
EnterCriticalSection(&g_executor_lock);
|
|
284
|
-
req.next = g_request_list;
|
|
285
|
-
g_request_list = &req;
|
|
286
|
-
WakeConditionVariable(&g_executor_cond);
|
|
287
|
-
LeaveCriticalSection(&g_executor_lock);
|
|
288
|
-
|
|
289
|
-
/* Wait for the executor to process our callback */
|
|
290
|
-
EnterCriticalSection(&req.done_lock);
|
|
291
|
-
while (!req.done) {
|
|
292
|
-
SleepConditionVariableCS(&req.done_cond, &req.done_lock, INFINITE);
|
|
293
|
-
}
|
|
294
|
-
LeaveCriticalSection(&req.done_lock);
|
|
295
|
-
|
|
296
|
-
DeleteCriticalSection(&req.done_lock);
|
|
297
|
-
#else
|
|
298
|
-
pthread_mutex_init(&req.done_mutex, NULL);
|
|
299
|
-
pthread_cond_init(&req.done_cond, NULL);
|
|
286
|
+
sync_mutex_init(&req.done_lock);
|
|
287
|
+
sync_cond_init(&req.done_cond);
|
|
300
288
|
|
|
301
289
|
/* Enqueue the request */
|
|
302
|
-
|
|
290
|
+
sync_lock(&g_executor_lock);
|
|
303
291
|
req.next = g_request_list;
|
|
304
292
|
g_request_list = &req;
|
|
305
|
-
|
|
306
|
-
|
|
293
|
+
sync_signal(&g_executor_cond);
|
|
294
|
+
sync_unlock(&g_executor_lock);
|
|
307
295
|
|
|
308
296
|
/* Wait for the executor to process our callback */
|
|
309
|
-
|
|
310
|
-
while (!req.done) {
|
|
311
|
-
pthread_cond_wait(&req.done_cond, &req.done_mutex);
|
|
312
|
-
}
|
|
313
|
-
pthread_mutex_unlock(&req.done_mutex);
|
|
297
|
+
sync_wait_for_flag(&req.done_lock, &req.done_cond, &req.done);
|
|
314
298
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
#endif
|
|
299
|
+
sync_cond_destroy(&req.done_cond);
|
|
300
|
+
sync_mutex_destroy(&req.done_lock);
|
|
318
301
|
}
|
|
319
302
|
|
|
320
303
|
/* Payload for rb_thread_call_with_gvl wrapper */
|
|
@@ -353,55 +336,36 @@ struct worker_proxy {
|
|
|
353
336
|
volatile int has_request;
|
|
354
337
|
volatile int request_done;
|
|
355
338
|
volatile int thread_exited;
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
CONDITION_VARIABLE thread_exit_cond;
|
|
361
|
-
#else
|
|
362
|
-
pthread_mutex_t lock;
|
|
363
|
-
pthread_cond_t request_cond;
|
|
364
|
-
pthread_cond_t request_done_cond;
|
|
365
|
-
pthread_cond_t thread_exit_cond;
|
|
366
|
-
#endif
|
|
339
|
+
sync_mutex_t lock;
|
|
340
|
+
sync_cond_t request_cond;
|
|
341
|
+
sync_cond_t request_done_cond;
|
|
342
|
+
sync_cond_t thread_exit_cond;
|
|
367
343
|
};
|
|
368
344
|
|
|
369
|
-
/*
|
|
345
|
+
/*
|
|
346
|
+
* Runs without GVL: the proxy waits for a callback request. Returns non-NULL
|
|
347
|
+
* if the request should run, NULL if the proxy should stop. The flags are
|
|
348
|
+
* read here, under the lock, so the caller never reads them unlocked.
|
|
349
|
+
*/
|
|
370
350
|
static void *proxy_wait_func(void *data) {
|
|
371
351
|
struct worker_proxy *proxy = (struct worker_proxy *)data;
|
|
352
|
+
int run;
|
|
372
353
|
|
|
373
|
-
|
|
374
|
-
EnterCriticalSection(&proxy->lock);
|
|
375
|
-
while (!proxy->stop_requested && !proxy->has_request) {
|
|
376
|
-
SleepConditionVariableCS(&proxy->request_cond, &proxy->lock, INFINITE);
|
|
377
|
-
}
|
|
378
|
-
LeaveCriticalSection(&proxy->lock);
|
|
379
|
-
#else
|
|
380
|
-
pthread_mutex_lock(&proxy->lock);
|
|
354
|
+
sync_lock(&proxy->lock);
|
|
381
355
|
while (!proxy->stop_requested && !proxy->has_request) {
|
|
382
|
-
|
|
356
|
+
sync_wait(&proxy->request_cond, &proxy->lock);
|
|
383
357
|
}
|
|
384
|
-
|
|
385
|
-
|
|
358
|
+
run = !proxy->stop_requested;
|
|
359
|
+
sync_unlock(&proxy->lock);
|
|
386
360
|
|
|
387
|
-
return NULL;
|
|
361
|
+
return run ? proxy : NULL;
|
|
388
362
|
}
|
|
389
363
|
|
|
390
364
|
/* Unblock function for the proxy thread (VM shutdown or Thread#kill) */
|
|
391
365
|
static void proxy_stop_func(void *data) {
|
|
392
366
|
struct worker_proxy *proxy = (struct worker_proxy *)data;
|
|
393
367
|
|
|
394
|
-
|
|
395
|
-
EnterCriticalSection(&proxy->lock);
|
|
396
|
-
proxy->stop_requested = 1;
|
|
397
|
-
WakeConditionVariable(&proxy->request_cond);
|
|
398
|
-
LeaveCriticalSection(&proxy->lock);
|
|
399
|
-
#else
|
|
400
|
-
pthread_mutex_lock(&proxy->lock);
|
|
401
|
-
proxy->stop_requested = 1;
|
|
402
|
-
pthread_cond_signal(&proxy->request_cond);
|
|
403
|
-
pthread_mutex_unlock(&proxy->lock);
|
|
404
|
-
#endif
|
|
368
|
+
sync_set_and_signal(&proxy->lock, &proxy->request_cond, &proxy->stop_requested);
|
|
405
369
|
}
|
|
406
370
|
|
|
407
371
|
static VALUE proxy_run_body(VALUE data) {
|
|
@@ -414,19 +378,11 @@ static VALUE proxy_run_body(VALUE data) {
|
|
|
414
378
|
static VALUE proxy_signal_done(VALUE data) {
|
|
415
379
|
struct worker_proxy *proxy = (struct worker_proxy *)data;
|
|
416
380
|
|
|
417
|
-
|
|
418
|
-
EnterCriticalSection(&proxy->lock);
|
|
419
|
-
proxy->has_request = 0;
|
|
420
|
-
proxy->request_done = 1;
|
|
421
|
-
WakeConditionVariable(&proxy->request_done_cond);
|
|
422
|
-
LeaveCriticalSection(&proxy->lock);
|
|
423
|
-
#else
|
|
424
|
-
pthread_mutex_lock(&proxy->lock);
|
|
381
|
+
sync_lock(&proxy->lock);
|
|
425
382
|
proxy->has_request = 0;
|
|
426
383
|
proxy->request_done = 1;
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
#endif
|
|
384
|
+
sync_signal(&proxy->request_done_cond);
|
|
385
|
+
sync_unlock(&proxy->lock);
|
|
430
386
|
|
|
431
387
|
return Qnil;
|
|
432
388
|
}
|
|
@@ -439,19 +395,13 @@ static VALUE proxy_run(VALUE data) {
|
|
|
439
395
|
static VALUE proxy_loop_body(VALUE data) {
|
|
440
396
|
struct worker_proxy *proxy = (struct worker_proxy *)data;
|
|
441
397
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
if (proxy->stop_requested) break;
|
|
447
|
-
|
|
448
|
-
if (proxy->has_request) {
|
|
449
|
-
int state = 0;
|
|
398
|
+
/* Release the GVL and wait for a request */
|
|
399
|
+
while (rb_thread_call_without_gvl(proxy_wait_func, proxy, proxy_stop_func, proxy) != NULL) {
|
|
400
|
+
int state = 0;
|
|
450
401
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}
|
|
402
|
+
rb_protect(proxy_run, data, &state);
|
|
403
|
+
if (state) {
|
|
404
|
+
discard_callback_exception(state);
|
|
455
405
|
}
|
|
456
406
|
}
|
|
457
407
|
|
|
@@ -475,19 +425,14 @@ static VALUE proxy_cleanup(VALUE data) {
|
|
|
475
425
|
/*
|
|
476
426
|
* Signal that this thread has finished and no longer touches the proxy
|
|
477
427
|
* struct. Only after this may rbduckdb_worker_proxy_destroy free it.
|
|
428
|
+
* Also wake a worker waiting on a request this thread will never run
|
|
429
|
+
* (e.g. Thread#kill while idle, #1512); it falls back to the executor.
|
|
478
430
|
*/
|
|
479
|
-
|
|
480
|
-
EnterCriticalSection(&proxy->lock);
|
|
481
|
-
proxy->thread_exited = 1;
|
|
482
|
-
WakeConditionVariable(&proxy->thread_exit_cond);
|
|
483
|
-
LeaveCriticalSection(&proxy->lock);
|
|
484
|
-
#else
|
|
485
|
-
pthread_mutex_lock(&proxy->lock);
|
|
431
|
+
sync_lock(&proxy->lock);
|
|
486
432
|
proxy->thread_exited = 1;
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
433
|
+
sync_signal(&proxy->thread_exit_cond);
|
|
434
|
+
sync_signal(&proxy->request_done_cond);
|
|
435
|
+
sync_unlock(&proxy->lock);
|
|
491
436
|
return Qnil;
|
|
492
437
|
}
|
|
493
438
|
|
|
@@ -496,12 +441,39 @@ static VALUE proxy_thread_func(void *data) {
|
|
|
496
441
|
return rb_ensure(proxy_loop_body, (VALUE)data, proxy_cleanup, (VALUE)data);
|
|
497
442
|
}
|
|
498
443
|
|
|
444
|
+
/* Tear down the OS primitives and free the struct. The proxy thread must be gone. */
|
|
445
|
+
static void proxy_free(struct worker_proxy *proxy) {
|
|
446
|
+
sync_cond_destroy(&proxy->thread_exit_cond);
|
|
447
|
+
sync_cond_destroy(&proxy->request_done_cond);
|
|
448
|
+
sync_cond_destroy(&proxy->request_cond);
|
|
449
|
+
sync_mutex_destroy(&proxy->lock);
|
|
450
|
+
|
|
451
|
+
free(proxy);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
static VALUE proxy_start_thread(VALUE data) {
|
|
455
|
+
struct worker_proxy *proxy = (struct worker_proxy *)data;
|
|
456
|
+
|
|
457
|
+
proxy->ruby_thread = rb_thread_create(proxy_thread_func, proxy);
|
|
458
|
+
rb_ary_push(g_proxy_threads, proxy->ruby_thread);
|
|
459
|
+
return Qnil;
|
|
460
|
+
}
|
|
461
|
+
|
|
499
462
|
struct worker_proxy *rbduckdb_worker_proxy_create(void) {
|
|
463
|
+
struct worker_proxy *proxy;
|
|
464
|
+
int state = 0;
|
|
465
|
+
|
|
466
|
+
/*
|
|
467
|
+
* Before allocating so a raise here leaks nothing; create runs with the
|
|
468
|
+
* GVL, so safe.
|
|
469
|
+
*/
|
|
470
|
+
proxy_threads_ensure();
|
|
471
|
+
|
|
500
472
|
/*
|
|
501
473
|
* Use calloc (not xcalloc): rbduckdb_worker_proxy_destroy frees the struct
|
|
502
474
|
* from a non-Ruby thread where xfree is unsafe.
|
|
503
475
|
*/
|
|
504
|
-
|
|
476
|
+
proxy = calloc(1, sizeof(struct worker_proxy));
|
|
505
477
|
if (proxy == NULL) {
|
|
506
478
|
rb_raise(rb_eNoMemError, "failed to allocate worker_proxy");
|
|
507
479
|
}
|
|
@@ -511,87 +483,101 @@ struct worker_proxy *rbduckdb_worker_proxy_create(void) {
|
|
|
511
483
|
proxy->request_done = 0;
|
|
512
484
|
proxy->thread_exited = 0;
|
|
513
485
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
InitializeConditionVariable(&proxy->thread_exit_cond);
|
|
519
|
-
#else
|
|
520
|
-
pthread_mutex_init(&proxy->lock, NULL);
|
|
521
|
-
pthread_cond_init(&proxy->request_cond, NULL);
|
|
522
|
-
pthread_cond_init(&proxy->request_done_cond, NULL);
|
|
523
|
-
pthread_cond_init(&proxy->thread_exit_cond, NULL);
|
|
524
|
-
#endif
|
|
486
|
+
sync_mutex_init(&proxy->lock);
|
|
487
|
+
sync_cond_init(&proxy->request_cond);
|
|
488
|
+
sync_cond_init(&proxy->request_done_cond);
|
|
489
|
+
sync_cond_init(&proxy->thread_exit_cond);
|
|
525
490
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
491
|
+
rb_protect(proxy_start_thread, (VALUE)proxy, &state);
|
|
492
|
+
if (state) {
|
|
493
|
+
if (proxy->ruby_thread) {
|
|
494
|
+
/*
|
|
495
|
+
* The thread started but could not be registered (rb_ary_push
|
|
496
|
+
* raised NoMemError): stop and join it. Like any destroy, the join
|
|
497
|
+
* has no unblock function; see #1512 for the hang analysis.
|
|
498
|
+
*/
|
|
499
|
+
rbduckdb_worker_proxy_destroy(proxy);
|
|
500
|
+
} else {
|
|
501
|
+
/* rb_thread_create raised: no thread ever touched the proxy. */
|
|
502
|
+
proxy_free(proxy);
|
|
503
|
+
}
|
|
504
|
+
rb_jump_tag(state);
|
|
533
505
|
}
|
|
534
506
|
|
|
535
|
-
proxy->ruby_thread = rb_thread_create(proxy_thread_func, proxy);
|
|
536
|
-
rb_ary_push(g_proxy_threads, proxy->ruby_thread);
|
|
537
|
-
|
|
538
507
|
return proxy;
|
|
539
508
|
}
|
|
540
509
|
|
|
510
|
+
struct create_proxy_callback_arg {
|
|
511
|
+
struct worker_proxy *proxy;
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
static VALUE create_proxy_callback(VALUE varg) {
|
|
515
|
+
struct create_proxy_callback_arg *arg = (struct create_proxy_callback_arg *)varg;
|
|
516
|
+
arg->proxy = rbduckdb_worker_proxy_create();
|
|
517
|
+
return Qnil;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/*
|
|
521
|
+
* rbduckdb_worker_proxy_create may raise (NoMemError, Thread.new failure),
|
|
522
|
+
* and the executor runs callbacks unprotected - a raise would longjmp past
|
|
523
|
+
* its done-signaling and block the waiting DuckDB worker forever. Swallow
|
|
524
|
+
* the exception instead: the proxy stays NULL and the caller falls back to
|
|
525
|
+
* the global executor.
|
|
526
|
+
*/
|
|
527
|
+
static void create_proxy_callback_protected(void *user_data) {
|
|
528
|
+
int exception_state;
|
|
529
|
+
|
|
530
|
+
rb_protect(create_proxy_callback, (VALUE)user_data, &exception_state);
|
|
531
|
+
if (exception_state) {
|
|
532
|
+
rb_set_errinfo(Qnil);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
struct worker_proxy *rbduckdb_worker_proxy_create_for_current_thread(void) {
|
|
537
|
+
struct create_proxy_callback_arg arg;
|
|
538
|
+
|
|
539
|
+
/* A Ruby calling thread runs the callback inline (Case 1/2); no proxy needed. */
|
|
540
|
+
if (ruby_native_thread_p()) return NULL;
|
|
541
|
+
|
|
542
|
+
arg.proxy = NULL;
|
|
543
|
+
rbduckdb_function_executor_dispatch(create_proxy_callback_protected, &arg);
|
|
544
|
+
return arg.proxy;
|
|
545
|
+
}
|
|
546
|
+
|
|
541
547
|
/*
|
|
542
548
|
* Hand a callback to a proxy and block until it completes.
|
|
543
549
|
* Called from the DuckDB worker thread (non-Ruby thread) that owns this proxy.
|
|
550
|
+
* If the proxy thread has exited (or exits before running the request), the
|
|
551
|
+
* callback goes to the global executor instead.
|
|
544
552
|
*/
|
|
545
553
|
static void dispatch_callback_to_proxy(struct worker_proxy *proxy, rbduckdb_function_callback_t cb, void *user_data) {
|
|
546
|
-
|
|
547
|
-
EnterCriticalSection(&proxy->lock);
|
|
548
|
-
proxy->cb = cb;
|
|
549
|
-
proxy->user_data = user_data;
|
|
550
|
-
proxy->request_done = 0;
|
|
551
|
-
proxy->has_request = 1;
|
|
552
|
-
WakeConditionVariable(&proxy->request_cond);
|
|
553
|
-
LeaveCriticalSection(&proxy->lock);
|
|
554
|
+
int done;
|
|
554
555
|
|
|
555
|
-
|
|
556
|
-
while (!proxy->request_done) {
|
|
557
|
-
SleepConditionVariableCS(&proxy->request_done_cond, &proxy->lock, INFINITE);
|
|
558
|
-
}
|
|
559
|
-
LeaveCriticalSection(&proxy->lock);
|
|
560
|
-
#else
|
|
561
|
-
pthread_mutex_lock(&proxy->lock);
|
|
562
|
-
proxy->cb = cb;
|
|
563
|
-
proxy->user_data = user_data;
|
|
556
|
+
sync_lock(&proxy->lock);
|
|
564
557
|
proxy->request_done = 0;
|
|
565
|
-
proxy->
|
|
566
|
-
|
|
567
|
-
|
|
558
|
+
if (!proxy->thread_exited) {
|
|
559
|
+
proxy->cb = cb;
|
|
560
|
+
proxy->user_data = user_data;
|
|
561
|
+
proxy->has_request = 1;
|
|
562
|
+
sync_signal(&proxy->request_cond);
|
|
563
|
+
while (!proxy->request_done && !proxy->thread_exited) {
|
|
564
|
+
sync_wait(&proxy->request_done_cond, &proxy->lock);
|
|
565
|
+
}
|
|
566
|
+
proxy->has_request = 0;
|
|
567
|
+
}
|
|
568
|
+
done = proxy->request_done;
|
|
569
|
+
sync_unlock(&proxy->lock);
|
|
568
570
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
pthread_cond_wait(&proxy->request_done_cond, &proxy->lock);
|
|
571
|
+
if (!done) {
|
|
572
|
+
dispatch_callback_to_executor(cb, user_data);
|
|
572
573
|
}
|
|
573
|
-
pthread_mutex_unlock(&proxy->lock);
|
|
574
|
-
#endif
|
|
575
574
|
}
|
|
576
575
|
|
|
577
576
|
/* Blocks until the proxy thread has fully exited. Runs without the GVL. */
|
|
578
577
|
static void *proxy_join_func(void *data) {
|
|
579
578
|
struct worker_proxy *proxy = (struct worker_proxy *)data;
|
|
580
579
|
|
|
581
|
-
|
|
582
|
-
EnterCriticalSection(&proxy->lock);
|
|
583
|
-
while (!proxy->thread_exited) {
|
|
584
|
-
SleepConditionVariableCS(&proxy->thread_exit_cond, &proxy->lock, INFINITE);
|
|
585
|
-
}
|
|
586
|
-
LeaveCriticalSection(&proxy->lock);
|
|
587
|
-
#else
|
|
588
|
-
pthread_mutex_lock(&proxy->lock);
|
|
589
|
-
while (!proxy->thread_exited) {
|
|
590
|
-
pthread_cond_wait(&proxy->thread_exit_cond, &proxy->lock);
|
|
591
|
-
}
|
|
592
|
-
pthread_mutex_unlock(&proxy->lock);
|
|
593
|
-
#endif
|
|
594
|
-
|
|
580
|
+
sync_wait_for_flag(&proxy->lock, &proxy->thread_exit_cond, &proxy->thread_exited);
|
|
595
581
|
return NULL;
|
|
596
582
|
}
|
|
597
583
|
|
|
@@ -600,17 +586,7 @@ void rbduckdb_worker_proxy_destroy(void *data) {
|
|
|
600
586
|
if (proxy == NULL) return;
|
|
601
587
|
|
|
602
588
|
/* Ask the proxy thread to stop. */
|
|
603
|
-
|
|
604
|
-
EnterCriticalSection(&proxy->lock);
|
|
605
|
-
proxy->stop_requested = 1;
|
|
606
|
-
WakeConditionVariable(&proxy->request_cond);
|
|
607
|
-
LeaveCriticalSection(&proxy->lock);
|
|
608
|
-
#else
|
|
609
|
-
pthread_mutex_lock(&proxy->lock);
|
|
610
|
-
proxy->stop_requested = 1;
|
|
611
|
-
pthread_cond_signal(&proxy->request_cond);
|
|
612
|
-
pthread_mutex_unlock(&proxy->lock);
|
|
613
|
-
#endif
|
|
589
|
+
proxy_stop_func(proxy);
|
|
614
590
|
|
|
615
591
|
/*
|
|
616
592
|
* Wait until the proxy thread has fully exited. Before exiting it runs Ruby
|
|
@@ -626,17 +602,7 @@ void rbduckdb_worker_proxy_destroy(void *data) {
|
|
|
626
602
|
proxy_join_func(proxy);
|
|
627
603
|
}
|
|
628
604
|
|
|
629
|
-
|
|
630
|
-
#ifdef _MSC_VER
|
|
631
|
-
DeleteCriticalSection(&proxy->lock);
|
|
632
|
-
#else
|
|
633
|
-
pthread_cond_destroy(&proxy->thread_exit_cond);
|
|
634
|
-
pthread_cond_destroy(&proxy->request_done_cond);
|
|
635
|
-
pthread_cond_destroy(&proxy->request_cond);
|
|
636
|
-
pthread_mutex_destroy(&proxy->lock);
|
|
637
|
-
#endif
|
|
638
|
-
|
|
639
|
-
free(proxy);
|
|
605
|
+
proxy_free(proxy);
|
|
640
606
|
}
|
|
641
607
|
|
|
642
608
|
void rbduckdb_function_executor_dispatch_via_proxy(rbduckdb_function_callback_t cb, void *user_data, struct worker_proxy *proxy) {
|
|
@@ -72,6 +72,16 @@ struct worker_proxy;
|
|
|
72
72
|
*/
|
|
73
73
|
struct worker_proxy *rbduckdb_worker_proxy_create(void);
|
|
74
74
|
|
|
75
|
+
/*
|
|
76
|
+
* Create a per-worker proxy for the calling DuckDB worker thread, for use from
|
|
77
|
+
* a per-worker init callback. Returns NULL when called from a Ruby thread (the
|
|
78
|
+
* callback then runs inline, no proxy needed) or when creation failed (the
|
|
79
|
+
* exception is swallowed; callers fall back to the global executor). The caller
|
|
80
|
+
* stores the proxy as its state, with rbduckdb_worker_proxy_destroy as the
|
|
81
|
+
* destructor.
|
|
82
|
+
*/
|
|
83
|
+
struct worker_proxy *rbduckdb_worker_proxy_create_for_current_thread(void);
|
|
84
|
+
|
|
75
85
|
/*
|
|
76
86
|
* Destroy a per-worker proxy. The signature matches duckdb_delete_callback_t so
|
|
77
87
|
* it can be handed directly to DuckDB. Safe to call from a non-Ruby thread: it
|
|
@@ -219,3 +219,29 @@ void rbduckdb_vector_set_value_at(duckdb_vector vector, duckdb_logical_type elem
|
|
|
219
219
|
break;
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
|
+
|
|
223
|
+
void rbduckdb_input_columns_load(duckdb_data_chunk input, idx_t n, duckdb_vector **vectors, duckdb_logical_type **types) {
|
|
224
|
+
idx_t j;
|
|
225
|
+
|
|
226
|
+
*vectors = ALLOC_N(duckdb_vector, n);
|
|
227
|
+
*types = ALLOC_N(duckdb_logical_type, n);
|
|
228
|
+
|
|
229
|
+
for (j = 0; j < n; j++) {
|
|
230
|
+
(*vectors)[j] = duckdb_data_chunk_get_vector(input, j);
|
|
231
|
+
(*types)[j] = duckdb_vector_get_column_type((*vectors)[j]);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
void rbduckdb_input_columns_free(duckdb_vector *vectors, duckdb_logical_type *types, idx_t n) {
|
|
236
|
+
idx_t j;
|
|
237
|
+
|
|
238
|
+
if (types != NULL) {
|
|
239
|
+
for (j = 0; j < n; j++) {
|
|
240
|
+
duckdb_destroy_logical_type(&types[j]);
|
|
241
|
+
}
|
|
242
|
+
xfree(types);
|
|
243
|
+
}
|
|
244
|
+
if (vectors != NULL) {
|
|
245
|
+
xfree(vectors);
|
|
246
|
+
}
|
|
247
|
+
}
|