Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ${{ matrix.os }}
env:
# The oldest iyi this release supports (README, Requirements).
IYI_VERSION: 0.20.0
IYI_VERSION: 0.21.0
steps:
- uses: actions/checkout@v4

Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
## Unreleased

Requires iyi 0.21.0. Parallel tasks are spelled `spawn(unit: :new)` there,
and 0.21.0 refuses raw memory and trust marks in a module that is not
marked `@[Unsafe("why")]` (SPEC.md III.13), a mark 0.20.x refuses above a
`module` header.

* `db`, `db/pooling` and `db/testing/dummy_driver` are marked for the types
they hold `Share` on trust (`SharedBuilder`, `Pool` and `PoolSlot`,
`Made`), and `db/core` for the pool a `Database` leaves uninitialized
while it builds it, each mark saying why.
* The tests and manual programs start parallel tasks with
`spawn(unit: :new)`, and the tests read a string's bytes with
`Bytes.of`.

## v0.4.0

Requires iyi 0.20.0: a `Database` shared by tasks on every worker needs
Expand Down
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ require: errors are values, types are closed, and tasks are structured.

## Requirements

iyi 0.20.0 or later: a `Database` shared by tasks on every worker needs
`spawn_parallel` (0.19.0), the `@[Share]` defs and `SharedProc` that check
the blocks it keeps (0.19.1), and 0.20.0's scheduler, on which a checkout
parked on a full pool is woken when a connection comes back.
iyi 0.21.0 or later: a `Database` shared by tasks on every worker needs
parallel tasks (`spawn_parallel` in 0.19.0, spelled `spawn(unit: :new)`
since 0.21.0, which is what the tests and manual programs write), the
`@[Share]` defs and `SharedProc` that check the blocks it keeps (0.19.1),
and 0.20.0's scheduler, on which a checkout parked on a full pool is woken
when a connection comes back.

## Installation

Expand Down Expand Up @@ -95,7 +97,7 @@ choose how statements run.
### On every worker

A `Database` is `Share`, so one opened once and kept in a constant serves
parallel tasks (`spawn_parallel`) on every worker, a web server's handlers
parallel tasks (`spawn(unit: :new)`) on every worker, a web server's handlers
among them, with no `Share` error and no warning from the compiler:

```iyi
Expand All @@ -106,7 +108,7 @@ def ask(n : Int64) : Int64
end

group do |g|
32.times { |t| g.spawn_parallel { ask(t.to_i64); nil } }
32.times { |t| g.spawn(unit: :new) { ask(t.to_i64); nil } }
end
```

Expand Down Expand Up @@ -192,7 +194,7 @@ crystal-db's.
| `DB::MetadataValueConverter.arg_to_log` overloads | `CustomValue#to_log` | R-3 |
| `Pool.new` and `Database.new` open the initial connections | `Pool#prefill` and `Database.open(...)` do; the constructors do no IO (a constructor has no error to answer) | III.1 |
| `TopLevelTransaction.new` runs `BEGIN` | `Transaction#start` runs `BEGIN`/`SAVEPOINT`; `perform_commit`/`perform_rollback` are what a transaction subclass implements | III.1 |
| `Pool` guards itself with a `Mutex` | so does iyi-db's, around its bookkeeping, since tasks run on every worker (`spawn_parallel`); a connection goes in and out of the lock in a slot that is `Share` on trust, as no connection is `Share` | III.4.4, III.4.13 |
| `Pool` guards itself with a `Mutex` | so does iyi-db's, around its bookkeeping, since tasks run on every worker (`spawn(unit: :new)`); a connection goes in and out of the lock in a slot that is `Share` on trust, as no connection is `Share` | III.4.4, III.4.13 |
| a pool's factory, `setup_connection`'s block and `def_around_query_or_exec`'s hooks may capture anything | they run on whichever worker needs them, so they may capture only `Share` values, checked where they are written (`@[Share]`) | III.4.4: data races are a compile error |
| `disconnect!`-style names | no `!` in names | III.1.7 |
| `DB::DriverSpecs.run do ... end` (implicit receiver) | `DriverSpecs.run do \|specs\| specs.... end` | no `with ... yield` |
Expand Down
1 change: 1 addition & 0 deletions db.iyi
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
#
# `derive db_serializable` (module `db/serializable`) maps the rows of a
# result set to a struct or class.
@[Unsafe("SharedBuilder is taken as Share on trust: the pool calls a driver's builder from several workers at once, and a builder only reads what its URI was parsed into")]
module db

# The types live in `db/core`, `db/errors`, `db/value`, `db/pooling` and
Expand Down
3 changes: 2 additions & 1 deletion db/core.iyi
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
# `tx.commit` before a `return` that should commit.
# * Statement instrumentation is `Statement.def_around_query_or_exec { ... }`,
# a hook registry, because there is no reopening `DB::Statement` here (R-3).
@[Unsafe("a database's pool is left uninitialized while it is built, so that the factory the pool keeps can capture the database, and nothing reads it before it is assigned")]
module db/core

import std/uri::{URI, Params}
Expand Down Expand Up @@ -1275,7 +1276,7 @@ end
# ## Every worker
#
# A `Database` is `Share`: one kept in a constant may be used from parallel
# tasks (`spawn_parallel`) on every worker. Its pool hands each connection
# tasks (`spawn(unit: :new)`) on every worker. Its pool hands each connection
# to one task at a time (`Pool`), and the blocks it keeps - the connection
# factory and `setup_connection`'s - run on whichever worker opens a
# connection, so each is checked where it is written, as a parallel task's
Expand Down
2 changes: 1 addition & 1 deletion db/mapping_test.iyi
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import db/testing/dummy_driver::*

# crystal's `String#to_slice`, which iyi's prelude has not.
def bytes_of(text : String) : Bytes
Bytes.new(text.to_unsafe, text.bytesize).dup
Bytes.of(text).dup
end

class SimpleMapping
Expand Down
12 changes: 6 additions & 6 deletions db/parallel_test.iyi
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ end
def ask_everything(db : Database) : Nil
group do |g|
32.times do |t|
g.spawn_parallel do
g.spawn(unit: :new) do
2000.times do |k|
n = t.to_i64 * 100000_i64 + k.to_i64
got = db.query_one("?", n, as: Int64).or_panic
Expand All @@ -51,7 +51,7 @@ describe "a Database every worker shares (db#1)" do
it "answers the issue's program, through a constant, with the rows each task asked for" do
group do |g|
32.times do |t|
g.spawn_parallel do
g.spawn(unit: :new) do
2000.times do |k|
n = t.to_i64 * 100000_i64 + k.to_i64
got = ask(n)
Expand Down Expand Up @@ -83,7 +83,7 @@ describe "a Database every worker shares (db#1)" do
most = Atomic(Int32).new(0)
started = Time.monotonic
group do |g|
g.spawn_parallel do
g.spawn(unit: :new) do
a = db.checkout.or_panic
b = db.checkout.or_panic
go.close
Expand All @@ -93,7 +93,7 @@ describe "a Database every worker shares (db#1)" do
nil
end
40.times do |t|
g.spawn_parallel do
g.spawn(unit: :new) do
go.receive
n = t.to_i64
got = db.query_one("?", n, as: Int64).or_panic
Expand Down Expand Up @@ -124,7 +124,7 @@ describe "a Database every worker shares (db#1)" do
refused = Atomic(Int32).new(0)
group do |g|
32.times do |t|
g.spawn_parallel do
g.spawn(unit: :new) do
k = 0
while true
n = t.to_i64 * 100000_i64 + k.to_i64
Expand Down Expand Up @@ -156,7 +156,7 @@ describe "a Database every worker shares (db#1)" do
db = ::Db.open("dummy://host?initial_pool_size=4&max_pool_size=8&max_idle_pool_size=8&retry_attempts=2&retry_delay=0").or_panic
group do |g|
32.times do |t|
g.spawn_parallel do
g.spawn(unit: :new) do
200.times do |k|
n = t.to_i64 * 100000_i64 + k.to_i64
case k % 4
Expand Down
3 changes: 2 additions & 1 deletion db/pooling.iyi
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# pool knows the one it is given back by its `object_id`.
#
# **Every worker may share one pool.** Since iyi 0.19.0 a program's tasks
# run on a pool of worker threads (`spawn_parallel`), and a pool kept in a
# run on a pool of worker threads (`spawn(unit: :new)`), and a pool kept in a
# constant is reached by tasks on all of them at once. v0.3.0 kept its
# bookkeeping with no lock, from when iyi's tasks shared one thread, and two
# workers checking out at once were handed one connection (db#1). Now:
Expand Down Expand Up @@ -41,6 +41,7 @@
# when its arguments are - which `Pool(Connection)` is not, a connection not
# being `Share`. The pool owns its resources as `Mutex(T)` owns its value:
# a task reaches one only between a checkout and its release.
@[Unsafe("Pool and PoolSlot are taken as Share on trust: the book hands each slot, and the resource in it, to one task at a time behind its lock")]
module db/pooling

import std/uri::{Params}
Expand Down
2 changes: 1 addition & 1 deletion db/serializable_test.iyi
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import db/testing/dummy_driver::*

# crystal's `String#to_slice`, which iyi's prelude has not.
def bytes_of(text : String) : Bytes
Bytes.new(text.to_unsafe, text.bytesize).dup
Bytes.of(text).dup
end

class SimpleModel
Expand Down
3 changes: 2 additions & 1 deletion db/testing/dummy_driver.iyi
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
# set of `"fail_close"` answers an error when it is closed.
#
# Its connections, statements and result sets may be made and used on any
# worker (`spawn_parallel`): what it counts is atomic, and what it keeps for
# worker (`spawn(unit: :new)`): what it counts is atomic, and what it keeps for
# the tests to look at is behind a lock.
@[Unsafe("the double's Made is taken as Share on trust, since a test looks at one only in the task that used it or after that task has ended")]
module db/testing/dummy_driver

import std/uri::{URI, Params}
Expand Down
4 changes: 2 additions & 2 deletions manual/pool_concurrency.iyi
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# % iyi run manual/pool_concurrency.iyi -- --options="max_pool_size=5" --duration=30 --concurrency=4
#
# The tasks are parallel (`spawn_parallel`), as crystal's fibers were under
# The tasks are parallel (`spawn(unit: :new)`), as crystal's fibers were under
# `-Dpreview_mt`: they share one `Database` from every worker, as many
# workers as `IYI_WORKERS` says, or one per core.
module manual/pool_concurrency
Expand Down Expand Up @@ -60,7 +60,7 @@ puts "Starting test for #{duration} seconds..."
running = Atomic(Int32).new(1)
group do |g|
concurrency.times do
g.spawn_parallel do
g.spawn(unit: :new) do
while running.get == 1
db.scalar "1"
sleep 0
Expand Down
Loading