aim-sqlite3 2.9.6.1.rc1 → 2.9.6.1.rc2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43e86d899fe688464f75e560fbd568366ccd7a03065170aa8e634bc888de7ec0
4
- data.tar.gz: 93f35aebba061a7aceed8f51f66fec2354373befa946d41b3f97969e95c9ae1c
3
+ metadata.gz: eab04f9cd1ed8fc16ff52ab4400ade1beda465ce62caa952b2685a27baab35a0
4
+ data.tar.gz: 6b5024ffd281631ddb5057e2c539a406a1082f25e9240864b177a468dc2cc203
5
5
  SHA512:
6
- metadata.gz: cde63444c99ced87de1e331bb39210dd02b1938f861ef29f6b1b0aecd79ef92a138a1c44e7840009b64445c107ce6b27afc86c82c63ff5819d305dc0005f9d0d
7
- data.tar.gz: '08fe4986933a45180c049379a54fd7eb3d895d21c80912683e636c5448ed1e31b54a3c4ea4dd1a09016bc93662ca67a79fb56679713a33872252a920b445e9bd'
6
+ metadata.gz: 6489b602fbefbe331cb375d862769f22f965b7dc95652beaeb0a6ba69b0e5b9252764f90e97e7aa72065227f0762feed3e85865d2e549c7fee824398cc29e08d
7
+ data.tar.gz: b8d001bd989737e7ab68b3169bbb82e791dc2ce21c3b0f0492e418fbe40cfad973044ace20f1908634441d853fd7d7eeab39589923997bb2f7837651aca0fe88
data/README.md CHANGED
@@ -4,17 +4,21 @@
4
4
  >
5
5
  > - **`statement_timeout` honors its duration.** Upstream sets the deadline to "now", so long
6
6
  > queries are interrupted almost at once; the deadline is also cleared on `Statement#reset!`.
7
- > - **Queries run without the GVL.** `Statement#step` and `execute_batch2` release the GVL, so
8
- > other threads keep running during long queries, and `Thread#kill` / `Thread#raise` cancel a
9
- > running query through `sqlite3_interrupt`. Ruby callbacks (UDFs, aggregates, busy handler,
10
- > authorizer, collations, trace) take the GVL back first. See `ext/sqlite3/gvl.c`.
11
- > - **Published as `aim-sqlite3`**, versioned `<upstream>.<n>` (e.g. `2.9.6.1`) and released by
12
- > pushing tag `v2.9.6.1` on `main`. PR branches publish release candidates (`v2.9.6.1.rc1`,
13
- > `rc2`, …) to validate in apps before merging. Precompiled for x86_64/aarch64 Linux and x86_64/arm64 macOS, with a
14
- > source gem for anything else. The packaged sqlite is built with `SQLITE_MAX_ATTACHED=50`,
15
- > and every gem carries the `sqlite3` program from the same build at `SQLite3.cli_path`.
16
- > CI and releases use `aim-ci.yml` / `aim-release.yml`; upstream workflows are disabled in the
17
- > repository settings.
7
+ > - **Queries run without the GVL.** `Statement#step` and `execute_batch2` release the GVL, so other
8
+ > threads keep running during long queries. Each connection's progress handler checks for Ruby
9
+ > interrupts every 1000 steps: `Thread#kill`, `Thread#raise` and `throw` stop the query, while
10
+ > signals and `Thread#wakeup` let it continue. Ruby callbacks (UDFs, aggregates, busy handler,
11
+ > authorizer, collations, trace) take the GVL back first. A `Database` must not be used by two
12
+ > threads at once. See `ext/sqlite3/gvl.c`.
13
+ > - **Published as `aim-sqlite3`**, versioned `<upstream>.<n>` (e.g. `2.9.6.1`; `<n>` is
14
+ > `SQLite3::AIM_VERSION` in `lib/sqlite3/aim_version.rb`) and released by pushing tag `v2.9.6.1`
15
+ > on `main`. PR branches publish release candidates (`v2.9.6.1.rc1`, `rc2`, …) to validate in apps
16
+ > before merging. Precompiled for x86_64/aarch64 Linux and x86_64/arm64 macOS, with a source gem
17
+ > for anything else. The packaged sqlite is built with `SQLITE_MAX_ATTACHED=50`, and every gem
18
+ > carries the `sqlite3` program from the same build at `SQLite3.cli_path`. CI and releases use
19
+ > `aim-ci.yml` / `aim-release.yml`; upstream workflows are disabled in the repository settings.
20
+ > - **Fork tests** live in their own files (`test/test_aim_statement.rb`, `test/test_cli.rb`) so
21
+ > upstream's test files stay untouched.
18
22
  >
19
23
  > To pull in upstream: `git fetch upstream && git rebase upstream/main`.
20
24
 
@@ -198,6 +198,8 @@ sqlite3_database_unwrap(VALUE database)
198
198
  return ctx;
199
199
  }
200
200
 
201
+ static int rb_sqlite3_progress(void *context);
202
+
201
203
  static VALUE
202
204
  rb_sqlite3_open_v2(VALUE self, VALUE file, VALUE mode, VALUE zvfs)
203
205
  {
@@ -232,6 +234,8 @@ rb_sqlite3_open_v2(VALUE self, VALUE file, VALUE mode, VALUE zvfs)
232
234
  CHECK_MSG(ctx->db, status, msg);
233
235
  }
234
236
 
237
+ sqlite3_progress_handler(ctx->db, 1000, rb_sqlite3_progress, (void *)ctx);
238
+
235
239
  if (flags & SQLITE_OPEN_READONLY) {
236
240
  ctx->flags |= SQLITE3_RB_DATABASE_READONLY;
237
241
  }
@@ -471,6 +475,14 @@ rb_sqlite3_statement_timeout(void *context)
471
475
  return 0;
472
476
  }
473
477
 
478
+ /* Installed for the connection's lifetime, since sqlite allows one progress handler. */
479
+ static int
480
+ rb_sqlite3_progress(void *context)
481
+ {
482
+ sqlite3RubyPtr ctx = (sqlite3RubyPtr)context;
483
+ return rb_sqlite3_interrupted() || (ctx->stmt_timeout && rb_sqlite3_statement_timeout(ctx));
484
+ }
485
+
474
486
  /* call-seq: db.statement_timeout = ms
475
487
  *
476
488
  * Indicates that if a query lasts longer than the indicated number of
@@ -485,9 +497,6 @@ set_statement_timeout(VALUE self, VALUE milliseconds)
485
497
  TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
486
498
 
487
499
  ctx->stmt_timeout = NUM2INT(milliseconds);
488
- int n = NUM2INT(milliseconds) == 0 ? -1 : 1000;
489
-
490
- sqlite3_progress_handler(ctx->db, n, rb_sqlite3_statement_timeout, (void *)ctx);
491
500
 
492
501
  return self;
493
502
  }
@@ -1204,6 +1213,8 @@ rb_sqlite3_open16(VALUE self, VALUE file)
1204
1213
  CHECK_MSG(ctx->db, status, msg);
1205
1214
  }
1206
1215
 
1216
+ sqlite3_progress_handler(ctx->db, 1000, rb_sqlite3_progress, (void *)ctx);
1217
+
1207
1218
  return INT2NUM(status);
1208
1219
  }
1209
1220
 
data/ext/sqlite3/gvl.c CHANGED
@@ -10,34 +10,107 @@
10
10
  /* Per OS thread: set while this thread is inside a without_gvl sqlite call. */
11
11
  static SQLITE3_TLS int in_nogvl = 0;
12
12
 
13
- /* Unblocking function: Thread#kill / Thread#raise call this from another
14
- * thread; sqlite3_interrupt is thread-safe and makes the running call return
15
- * SQLITE_INTERRUPT. */
16
- static void
17
- interrupt_db(void *db)
13
+ typedef struct nogvl_call {
14
+ void *(*func)(void *);
15
+ void *data;
16
+ int status;
17
+ VALUE thread;
18
+ int pending_state;
19
+ VALUE pending_error;
20
+ struct nogvl_call *outer;
21
+ } nogvl_call_t;
22
+
23
+ /* Per OS thread: the innermost without_gvl call; a callback may run a nested one. */
24
+ static SQLITE3_TLS nogvl_call_t *current_call = NULL;
25
+
26
+ static VALUE
27
+ check_ints_body(VALUE UNUSED(arg))
18
28
  {
19
- sqlite3_interrupt((sqlite3 *)db);
29
+ rb_thread_check_ints();
30
+ return Qnil;
20
31
  }
21
32
 
22
33
  static void *
23
- step_body(void *stmt)
34
+ check_ints(void *ptr)
24
35
  {
25
- return (void *)(intptr_t)sqlite3_step((sqlite3_stmt *)stmt);
36
+ nogvl_call_t *call = (nogvl_call_t *)ptr;
37
+
38
+ rb_protect(check_ints_body, Qnil, &call->pending_state);
39
+ call->pending_error = rb_errinfo();
40
+ return NULL;
26
41
  }
27
42
 
43
+ /* Called from the progress handler. Ruby flags a thread for Thread#raise and Thread#kill, but also
44
+ * for signals and Thread#wakeup, so the flag alone must not stop the query: run what is pending
45
+ * (trap handlers included) and stop only if that raised.
46
+ *
47
+ * Taking the GVL here deadlocks if another thread holds it while blocked on this connection's
48
+ * mutex, so a Database must not be used by two threads at once. */
28
49
  int
29
- rb_sqlite3_step_without_gvl(sqlite3_stmt *stmt)
50
+ rb_sqlite3_interrupted(void)
30
51
  {
31
- int was_nogvl = in_nogvl;
32
- int status;
52
+ nogvl_call_t *call = current_call;
53
+
54
+ if (!in_nogvl || !rb_thread_interrupted(call->thread)) { return 0; }
55
+
56
+ rb_sqlite3_with_gvl(check_ints, call);
57
+ return call->pending_state != 0;
58
+ }
59
+
60
+ static VALUE
61
+ run_without_gvl(VALUE ptr)
62
+ {
63
+ nogvl_call_t *call = (nogvl_call_t *)ptr;
64
+ call->status = (int)(intptr_t)rb_thread_call_without_gvl(call->func, call->data, NULL, NULL);
65
+ return Qnil;
66
+ }
67
+
68
+ /* Runs even when rb_thread_call_without_gvl raises on its way out. */
69
+ static VALUE
70
+ end_call(VALUE ptr)
71
+ {
72
+ nogvl_call_t *call = (nogvl_call_t *)ptr;
73
+
74
+ in_nogvl = 0;
75
+ current_call = call->outer;
76
+ return Qnil;
77
+ }
33
78
 
79
+ /* errmsg: sqlite's message for the interrupted call, freed when the call raises instead */
80
+ static int
81
+ call_without_gvl(void *(*func)(void *), void *data, char **errmsg)
82
+ {
83
+ nogvl_call_t call = { .func = func, .data = data, .thread = rb_thread_current(),
84
+ .pending_error = Qnil, .outer = current_call };
85
+
86
+ current_call = &call;
34
87
  in_nogvl = 1;
35
- status = (int)(intptr_t)rb_thread_call_without_gvl(
36
- step_body, stmt, interrupt_db, sqlite3_db_handle(stmt));
88
+ rb_ensure(run_without_gvl, (VALUE)&call, end_call, (VALUE)&call);
89
+
90
+ if (call.pending_state) {
91
+ if (errmsg) { sqlite3_free(*errmsg); }
92
+ /* A trap handler run on the way out may have reset errinfo, so exceptions are re-raised
93
+ * from the captured value; throw and Thread#kill have no public API for that and rely on
94
+ * errinfo. throw's errinfo is internal data, not an object, hence the T_OBJECT check. */
95
+ if (RB_TYPE_P(call.pending_error, T_OBJECT) && rb_obj_is_kind_of(call.pending_error, rb_eException)) {
96
+ rb_exc_raise(call.pending_error);
97
+ }
98
+ rb_jump_tag(call.pending_state);
99
+ }
100
+
101
+ return call.status;
102
+ }
37
103
 
38
- in_nogvl = was_nogvl;
104
+ static void *
105
+ step_body(void *stmt)
106
+ {
107
+ return (void *)(intptr_t)sqlite3_step((sqlite3_stmt *)stmt);
108
+ }
39
109
 
40
- return status;
110
+ int
111
+ rb_sqlite3_step_without_gvl(sqlite3_stmt *stmt)
112
+ {
113
+ return call_without_gvl(step_body, stmt, NULL);
41
114
  }
42
115
 
43
116
  typedef struct {
@@ -61,14 +134,7 @@ rb_sqlite3_exec_without_gvl(sqlite3 *db, const char *sql, sqlite3_callback callb
61
134
  void *callback_arg, char **errmsg)
62
135
  {
63
136
  exec_args_t args = { db, sql, callback, callback_arg, errmsg };
64
- int was_nogvl = in_nogvl;
65
- int status;
66
-
67
- in_nogvl = 1;
68
- status = (int)(intptr_t)rb_thread_call_without_gvl(exec_body, &args, interrupt_db, db);
69
- in_nogvl = was_nogvl;
70
-
71
- return status;
137
+ return call_without_gvl(exec_body, &args, errmsg);
72
138
  }
73
139
 
74
140
  /* Callbacks also fire from sqlite calls made with the GVL held (prepare,
data/ext/sqlite3/gvl.h CHANGED
@@ -4,12 +4,13 @@
4
4
  #include <sqlite3_ruby.h>
5
5
 
6
6
  /* sqlite3_step and sqlite3_exec run without the GVL so other threads keep
7
- * running, and so Thread#kill / Thread#raise can cancel them through
8
- * sqlite3_interrupt. sqlite calls Ruby callbacks on the same thread, so each
7
+ * running, and so Thread#kill / Thread#raise can cancel them: each connection's progress
8
+ * handler asks rb_sqlite3_interrupted. sqlite calls Ruby callbacks on the same thread, so each
9
9
  * callback goes through rb_sqlite3_with_gvl to take the GVL back first. */
10
10
  int rb_sqlite3_step_without_gvl(sqlite3_stmt *stmt);
11
11
  int rb_sqlite3_exec_without_gvl(sqlite3 *db, const char *sql, sqlite3_callback callback,
12
12
  void *callback_arg, char **errmsg);
13
13
  void *rb_sqlite3_with_gvl(void *(*func)(void *), void *data);
14
+ int rb_sqlite3_interrupted(void);
14
15
 
15
16
  #endif
@@ -0,0 +1,5 @@
1
+ module SQLite3
2
+ # (String) aim-sqlite3's release on top of upstream's VERSION; the gem is versioned
3
+ # "#{VERSION}.#{AIM_VERSION}", e.g. "2.9.6.1.rc1"
4
+ AIM_VERSION = "1.rc2"
5
+ end
@@ -1,4 +1,4 @@
1
1
  module SQLite3
2
2
  # (String) the version of the sqlite3 gem, e.g. "2.1.1"
3
- VERSION = "2.9.6.1.rc1"
3
+ VERSION = "2.9.6"
4
4
  end
data/lib/sqlite3.rb CHANGED
@@ -17,4 +17,5 @@ module SQLite3
17
17
  end
18
18
 
19
19
  require "sqlite3/version_info"
20
+ require "sqlite3/aim_version"
20
21
  require "sqlite3/cli"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aim-sqlite3
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.9.6.1.rc1
4
+ version: 2.9.6.1.rc2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jamis Buck
@@ -68,6 +68,7 @@ files:
68
68
  - ext/sqlite3/statement.h
69
69
  - ext/sqlite3/timespec.h
70
70
  - lib/sqlite3.rb
71
+ - lib/sqlite3/aim_version.rb
71
72
  - lib/sqlite3/cli.rb
72
73
  - lib/sqlite3/constants.rb
73
74
  - lib/sqlite3/database.rb