OXYGENHUB

Repository creates run off the async workers (closes #335) 15d3f6f7871b

Author
Andy McCall @andymccall <[email protected]>
via
agent agent:claude@andymccall <[email protected]> · keyless, under your grant for changes,refs,seal · key fe963c7c3e95
When
· 2026-10-27 01:08 UTC
Signature
◈ sealed by agent:claude@andymccall
Hash
15d3f6f7871b9ba24296599a30f4188cde40d8831dff7bdaad16c717bfadd9f4
Depends on
166f6e9bf4f1 19bb31ff1f5c 1f78655a0466 7a91f1fd71b0 a7e6e8e6094c b5668108487f b64e2537d702 d23464c0ac60
Depended on by
40a43921ef42 668b2ad7495c
In bookmarks
main
In tags
v0.9.11 v0.9.15 v0.9.16 v0.9.19 v0.9.20 v0.9.21 v0.9.22 v0.9.23 v0.9.24 v0.9.7 v0.9.8 v0.9.9
Edits
8 atoms, 0 tree operations, 5 files

Browse the tree at this change

modified CHANGELOG.md +1 −0
…
3 unchanged lines
4 4
5 5
## Unreleased
6 6
7 +
- **Creating a repository no longer stalls the hub on a busy disk (#335).** A bare init makes about sixteen fsyncs, and on Earth's shared disk each waited behind every other write: one create took 26 s and held an async worker the whole time. Creates from the API, the web and drops now run on a blocking thread, as forks and bucket creates already did.
7 8
- **A Diffusion bump pins the upstream's default bookmark (#332).** A bump kept the bookmark name of the pin it replaced, so a subrepo first pinned at an upstream proposal's bookmark still named it after the proposal landed and Diffusion moved the pin to the default bookmark's state; a checkout then counted the subrepo as on another bookmark. A bump now names the upstream's default bookmark, unless it keeps pinned changes that bookmark lacks.
8 9
- **Listing landed proposals is quick, and pages (#331).** `oh proposal list --state landed` on a repository with a few hundred proposals timed out: the API worked out every proposal's projected state again, landed and closed ones too. Those now come from what was recorded, and the list takes `limit` (`oh proposal list --limit N`, 30 by default), newest first.
9 10
- **A big image's push no longer fails at the last step (#341).** The hub reads a whole image before it accepts the manifest, which on a busy disk outlasted `flow rootfs push`'s wait. The image could then be kept but never offered, and a retry read it all again. The hub now finishes and records the image even if the sender gives up, and a manifest it already keeps is recorded without reading the image again.
…
352 unchanged lines
modified crates/hub/src/api/mod.rs +1 −1
…
786 unchanged lines
787 787
    if db::repos::get(&app.db, &name.owner, &name.name).await?.is_some() || app.repos.exists(&name) {
788 788
        return Err(ApiError(StatusCode::CONFLICT, "that repository exists".into()));
789 789
    }
790 -
    app.repos.create(&name, None)?;
790 +
    app.repos.create_async(&name, None).await?;
791 791
    let row = db::repos::create(&app.db, &name.owner, &name.name, &body.description, body.visibility).await?;
792 792
    db::accounts::audit(&app.db, Some(me), "repo.created", &name.to_string(), "api").await.ok();
793 793
    Ok((StatusCode::CREATED, Json(repository(&app, &row))))
…
2406 unchanged lines
modified crates/hub/src/drops.rs +1 −1
…
231 unchanged lines
232 232
    let Some(name) = found else { return Err(bad("No free drop id was found; try again.")) };
233 233
    let mut repo_id = [0u8; 16];
234 234
    rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut repo_id);
235 -
    app.repos.create(&name, Some(repo_id))?;
235 +
    app.repos.create_async(&name, Some(repo_id)).await?;
236 236
    let id = name.drop_id().unwrap_or_default().to_string();
237 237
    let row = match db::repos::create_drop(&app.db, &account.username, &id, title, description, visibility(secret)).await {
238 238
        Ok(row) => row,
…
212 unchanged lines
modified crates/hub/src/repos/mod.rs +11 −0
…
117 unchanged lines
118 118
        })
119 119
    }
120 120
121 +
    /// `create` off the async workers (#335): a bare init makes about
122 +
    /// sixteen fsyncs, and on a busy disk each waits behind every other
123 +
    /// write, so a create once held a worker for 26 s and stalled the
124 +
    /// requests queued behind it.
125 +
    pub async fn create_async(self: &Arc<Self>, name: &RepoName, id: Option<[u8; 16]>) -> Result<(), RepoError> {
126 +
        let (repos, name) = (self.clone(), name.clone());
127 +
        tokio::task::spawn_blocking(move || repos.create(&name, id).map(drop))
128 +
            .await
129 +
            .map_err(|e| RepoError::Io(std::io::Error::other(e.to_string())))?
130 +
    }
131 +
121 132
    /// Create an empty, unbound bare repository whose objects and
122 133
    /// operation log are kept in the store at `url` (oxygenhub#96); the
123 134
    /// store's credentials must already be known to the opener.
…
193 unchanged lines
modified crates/hub/src/web/repo_settings.rs +1 −1
…
149 unchanged lines
150 150
        .ok();
151 151
        return Ok(Redirect::to(&name.url()).into_response());
152 152
    }
153 -
    app.repos.create(&name, None).map_err(|e| Error::from_repo(shell.clone(), e))?;
153 +
    app.repos.create_async(&name, None).await.map_err(|e| Error::from_repo(shell.clone(), e))?;
154 154
    db::repos::create(&app.db, &name.owner, &name.name, f.description.trim(), visibility)
155 155
        .await
156 156
        .map_err(|e| Error::from_db(shell.clone(), e))?;
…
1489 unchanged lines