aim-sqlite3 2.9.6.1.rc1 → 2.9.6.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/README.md +15 -11
- data/ext/sqlite3/database.c +14 -3
- data/ext/sqlite3/gvl.c +89 -23
- data/ext/sqlite3/gvl.h +3 -2
- data/lib/sqlite3/aim_version.rb +5 -0
- data/lib/sqlite3/version.rb +1 -1
- data/lib/sqlite3.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 132caf70af607720e4d50454eda4ca7be764e79cc615dfa112fe7f84165a1cec
|
|
4
|
+
data.tar.gz: eeba8a4aeac89921f93047d02194b4a5df013bab24d6a712436c737586ec2c98
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 18f7beb3c30c88a2ebafea3d3815e5881f61d747fd80d8187491d6d029f9ed77d080f5a5cd3358b73073dfe845e9e7b2d60317fbf33dff672b8be168aeb752a3
|
|
7
|
+
data.tar.gz: 88b51d43013b21766b70766a6bfa1a89867b7965ff3e0197d094c5d549fa0add864f4938836f3c60bda3003f77c6433ea38d11d4d0de42a5d033f52f815a281b
|
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
|
-
>
|
|
9
|
-
>
|
|
10
|
-
>
|
|
11
|
-
>
|
|
12
|
-
>
|
|
13
|
-
>
|
|
14
|
-
>
|
|
15
|
-
>
|
|
16
|
-
>
|
|
17
|
-
>
|
|
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
|
|
data/ext/sqlite3/database.c
CHANGED
|
@@ -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
|
-
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
29
|
+
rb_thread_check_ints();
|
|
30
|
+
return Qnil;
|
|
20
31
|
}
|
|
21
32
|
|
|
22
33
|
static void *
|
|
23
|
-
|
|
34
|
+
check_ints(void *ptr)
|
|
24
35
|
{
|
|
25
|
-
|
|
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
|
-
|
|
50
|
+
rb_sqlite3_interrupted(void)
|
|
30
51
|
{
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
|
|
104
|
+
static void *
|
|
105
|
+
step_body(void *stmt)
|
|
106
|
+
{
|
|
107
|
+
return (void *)(intptr_t)sqlite3_step((sqlite3_stmt *)stmt);
|
|
108
|
+
}
|
|
39
109
|
|
|
40
|
-
|
|
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
|
-
|
|
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
|
|
8
|
-
*
|
|
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
|
data/lib/sqlite3/version.rb
CHANGED
data/lib/sqlite3.rb
CHANGED
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
|
|
4
|
+
version: 2.9.6.1
|
|
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
|