Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve on_progress #68

Merged
merged 11 commits into from
Feb 7, 2024
60 changes: 46 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -877,8 +877,7 @@ that specifies the approximate number of SQLite VM instructions between
successive calls to the progress handler:

```ruby
# Run progress handler every 100 SQLite VM instructions
db.on_progress(100) do
db.on_progress do
check_for_timeout
# Allow other threads to run
Thread.pass
Expand All @@ -891,7 +890,7 @@ above, calling `#interrupt` causes the query to raise a
`Extralite::InterruptError` exception:

```ruby
db.on_progress(100) { db.interrupt }
db.on_progress { db.interrupt }
db.query('select 1')
#=> Extralite::InterruptError!
```
Expand All @@ -900,7 +899,7 @@ You can also interrupt queries in progress by raising an exception. The query
will be stopped, and the exception will propagate to the call site:

```ruby
db.on_progress(100) do
db.on_progress do
raise 'BOOM!'
end

Expand All @@ -912,7 +911,7 @@ Here's how a timeout might be implemented using the progress handler:

```ruby
def setup_progress_handler
@db.on_progress(100) do
@db.on_progress do
raise TimeoutError if Time.now - @t0 >= @timeout
Thread.pass
end
Expand Down Expand Up @@ -956,15 +955,41 @@ This allows you to implement separate logic to deal with busy states, for
example sleeping for a small period of time, or implementing a different timeout
period.

### Tuning the Progress Handler Period
### Advanced Progress Handler Settings

You can further tune the behaviour of the progress handler with the following
options passed to `#on_progress`:

- `:mode`: the following modes are supported:
- `:none` : the progress handler is disabled.
- `:normal`: the progress handler is called on query progress (this is the
default mode).
- `:once`: the progress handler is called once before running the query.
- `:at_least_once`: the progress handler is called once before running the
query, and on query progress.
- `:period`: controls the approximate number of SQLite VM instructions executed
between consecutive calls to the progress handler. Default value: 1000.
- `:tick`: controls the granularity of the progress handler. This is the value
passed internally to the SQLite library. Default value: 10.

```ruby
db.on_progress(mode: :at_least_once, period: 640, tick: 5) { snooze }
```

The progress period passed to `#on_progress` determines how often the progress
handler will be called. For very simple queries and a big enough period, the
progress handler will not be called at all. On the other hand, setting the
period too low might hurt the performance of your queries, since there's some
overhead to invoking the progress handler, even if it does nothing. Therefore
you might want to experiment with different period values to see what offers the
best performance for your specific situation.
### Global Progress Handler Settings

You can set the global progress handler behaviour by calling
`Extralite.on_progress`. You can use this API to set the global progress
settings, without needing to set a progress handler individually for each
`Database` instance. This method takes the same options as
`Database#on_progress`:

```ruby
Extralite.on_progress(mode: :at_least_once, period: 640, tick: 5) { snooze }

# the new database instance uses the global progress handler settings
db = Database.new(':memory:')
```

### Extralite and Fibers

Expand Down Expand Up @@ -1016,7 +1041,14 @@ as long as the following conditions are met:

### Use with Ractors

Extralite databases can safely be used inside ractors. Note that ractors are still an experimental feature of Ruby. A ractor has the benefit of using a separate GVL from the maine one, which allows true parallelism for Ruby apps. So when you use Extralite to access SQLite databases from within a ractor, you can do so without any considerations for what's happening outside the ractor when it runs queries.
Extralite databases can safely be used inside ractors. A ractor has the benefit
of using a separate GVL from the maine one, which allows true parallelism for
Ruby apps. So when you use Extralite to access SQLite databases from within a
ractor, you can do so without any considerations for what's happening outside
the ractor when it runs queries.

**Note**: Ractors are considered an experimental feature of Ruby. You may
encounter errors or inconsistent behaviour when using ractors.

## Advanced Usage

Expand Down
8 changes: 4 additions & 4 deletions examples/pubsub_store_polyphony.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,9 @@ def prune_subscribers
db3 = Extralite::Database.new(fn)
db3.pragma(journal_mode: :wal, synchronous: 1)

db1.on_progress(50) { |b| b ? sleep(0.0001) : snooze }
db2.on_progress(50) { |b| b ? sleep(0.0001) : snooze }
db3.on_progress(50) { |b| b ? sleep(0.0001) : snooze }
db1.on_progress(1000) { |b| b ? sleep(0.0001) : snooze }
db2.on_progress(1000) { |b| b ? sleep(0.0001) : snooze }
db3.on_progress(1000) { |b| b ? sleep(0.0001) : snooze }

producer = PubSub.new(db1)
producer.setup
Expand Down Expand Up @@ -163,7 +163,7 @@ def prune_subscribers

db4 = Extralite::Database.new(fn)
db4.pragma(journal_mode: :wal, synchronous: 1)
db4.on_progress(10) { sleep 0.05 }
db4.on_progress(1000) { |busy| busy ? sleep(0.05) : snooze }

last_t = Time.now
last_publish_count = 0
Expand Down
23 changes: 15 additions & 8 deletions examples/pubsub_store_threads.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,21 @@ def unsubscribe(*topics)
end

def get_messages(&block)
messages = @db.query_ary('delete from messages where subscriber_id = ? returning topic, message', @id)
if block
messages.each(&block)
nil
else
messages
end
# end
@db.transaction(:deferred) do
results = @db.query_ary('select topic, message from messages where subscriber_id = ?', @id)
return [] if results.empty?

@db.execute('delete from messages where subscriber_id = ?', @id)
results
end

# messages = @db.query_ary('delete from messages where subscriber_id = ? returning topic, message', @id)
# if block
# messages.each(&block)
# nil
# else
# messages
# end
rescue Extralite::BusyError
p busy: :get_message
block_given? ? nil : []
Expand Down
6 changes: 3 additions & 3 deletions ext/extralite/changeset.c
Original file line number Diff line number Diff line change
Expand Up @@ -186,13 +186,13 @@ static inline VALUE convert_value(sqlite3_value *value) {
case SQLITE_BLOB:
{
int len = sqlite3_value_bytes(value);
void *blob = sqlite3_value_blob(value);
const void *blob = sqlite3_value_blob(value);
return rb_str_new(blob, len);
}
case SQLITE_TEXT:
{
int len = sqlite3_value_bytes(value);
void *text = sqlite3_value_text(value);
const void *text = sqlite3_value_text(value);
return rb_enc_str_new(text, len, UTF8_ENCODING);
}
default:
Expand Down Expand Up @@ -339,7 +339,7 @@ VALUE Changeset_to_a(VALUE self) {

// copied from: https://sqlite.org/sessionintro.html
static int xConflict(void *pCtx, int eConflict, sqlite3_changeset_iter *pIter){
int ret = (long)pCtx;
int ret = (int)pCtx;
return ret;
}

Expand Down
3 changes: 3 additions & 0 deletions ext/extralite/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,7 @@ static inline VALUE batch_run_array(query_ctx *ctx, enum batch_mode batch_mode)
for (int i = 0; i < count; i++) {
sqlite3_reset(ctx->stmt);
sqlite3_clear_bindings(ctx->stmt);
Database_issue_query(ctx->db, ctx->sql);
bind_all_parameters_from_object(ctx->stmt, RARRAY_AREF(ctx->params, i));

batch_iterate(ctx, batch_mode, &rows);
Expand Down Expand Up @@ -623,6 +624,7 @@ static VALUE batch_run_each_iter(RB_BLOCK_CALL_FUNC_ARGLIST(yield_value, vctx))

sqlite3_reset(each_ctx->ctx->stmt);
sqlite3_clear_bindings(each_ctx->ctx->stmt);
Database_issue_query(each_ctx->ctx->db, each_ctx->ctx->sql);
bind_all_parameters_from_object(each_ctx->ctx->stmt, yield_value);

batch_iterate(each_ctx->ctx, each_ctx->batch_mode, &rows);
Expand Down Expand Up @@ -668,6 +670,7 @@ static inline VALUE batch_run_proc(query_ctx *ctx, enum batch_mode batch_mode) {

sqlite3_reset(ctx->stmt);
sqlite3_clear_bindings(ctx->stmt);
Database_issue_query(ctx->db, ctx->sql);
bind_all_parameters_from_object(ctx->stmt, params);

batch_iterate(ctx, batch_mode, &rows);
Expand Down
Loading