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.
@@ -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
- #ifdef _MSC_VER
49
- CRITICAL_SECTION done_lock;
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
- /* Global executor state */
59
- #ifdef _MSC_VER
60
- static CRITICAL_SECTION g_executor_lock;
61
- static CONDITION_VARIABLE g_executor_cond;
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
- #ifdef _MSC_VER
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
- pthread_cond_wait(&g_executor_cond, &g_executor_mutex);
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
- pthread_mutex_unlock(&g_executor_mutex);
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
- #ifdef _MSC_VER
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
- #ifdef _MSC_VER
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
- InitializeCriticalSection(&g_executor_lock);
250
- InitializeConditionVariable(&g_executor_cond);
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
- if (g_proxy_threads == Qnil) {
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
- #ifdef _MSC_VER
279
- InitializeCriticalSection(&req.done_lock);
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
- pthread_mutex_lock(&g_executor_mutex);
290
+ sync_lock(&g_executor_lock);
303
291
  req.next = g_request_list;
304
292
  g_request_list = &req;
305
- pthread_cond_signal(&g_executor_cond);
306
- pthread_mutex_unlock(&g_executor_mutex);
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
- pthread_mutex_lock(&req.done_mutex);
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
- pthread_cond_destroy(&req.done_cond);
316
- pthread_mutex_destroy(&req.done_mutex);
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
- #ifdef _MSC_VER
357
- CRITICAL_SECTION lock;
358
- CONDITION_VARIABLE request_cond;
359
- CONDITION_VARIABLE request_done_cond;
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
- /* Runs without GVL: the proxy waits for a callback request */
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
- #ifdef _MSC_VER
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
- pthread_cond_wait(&proxy->request_cond, &proxy->lock);
356
+ sync_wait(&proxy->request_cond, &proxy->lock);
383
357
  }
384
- pthread_mutex_unlock(&proxy->lock);
385
- #endif
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
- #ifdef _MSC_VER
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
- #ifdef _MSC_VER
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
- pthread_cond_signal(&proxy->request_done_cond);
428
- pthread_mutex_unlock(&proxy->lock);
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
- while (!proxy->stop_requested) {
443
- /* Release the GVL and wait for a request */
444
- rb_thread_call_without_gvl(proxy_wait_func, proxy, proxy_stop_func, proxy);
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
- rb_protect(proxy_run, data, &state);
452
- if (state) {
453
- discard_callback_exception(state);
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
- #ifdef _MSC_VER
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
- pthread_cond_signal(&proxy->thread_exit_cond);
488
- pthread_mutex_unlock(&proxy->lock);
489
- #endif
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
- struct worker_proxy *proxy = calloc(1, sizeof(struct worker_proxy));
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
- #ifdef _MSC_VER
515
- InitializeCriticalSection(&proxy->lock);
516
- InitializeConditionVariable(&proxy->request_cond);
517
- InitializeConditionVariable(&proxy->request_done_cond);
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
- * Lazy-init the GC-protection array so create never silently skips it (see
528
- * the g_proxy_threads comment above); create runs with the GVL, so safe.
529
- */
530
- if (g_proxy_threads == Qnil) {
531
- g_proxy_threads = rb_ary_new();
532
- rb_global_variable(&g_proxy_threads);
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
- #ifdef _MSC_VER
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
- EnterCriticalSection(&proxy->lock);
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->has_request = 1;
566
- pthread_cond_signal(&proxy->request_cond);
567
- pthread_mutex_unlock(&proxy->lock);
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
- pthread_mutex_lock(&proxy->lock);
570
- while (!proxy->request_done) {
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
- #ifdef _MSC_VER
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
- #ifdef _MSC_VER
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
- /* The proxy thread is gone; tear down OS primitives and free the struct. */
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
+ }