-
Notifications
You must be signed in to change notification settings - Fork 15
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
test: add separate-thread concurrency test #305
Changes from all commits
4380b99
43726ef
f435854
c6df8dd
7691140
f39b422
d6373c0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,39 +14,34 @@ | |
|
||
import fs from 'node:fs/promises' | ||
import path from 'node:path' | ||
import { promisify } from 'node:util' | ||
// @ts-expect-error no types | ||
import fwa from 'fast-write-atomic' | ||
import { OpenFailedError, type AwaitIterable, PutFailedError, NotFoundError, DeleteFailedError } from 'interface-store' | ||
import glob from 'it-glob' | ||
import map from 'it-map' | ||
import parallelBatch from 'it-parallel-batch' | ||
import { Writer } from 'steno' | ||
import { NextToLast } from './sharding.js' | ||
import type { ShardingStrategy } from './sharding.js' | ||
import type { Blockstore, Pair } from 'interface-blockstore' | ||
import type { CID } from 'multiformats/cid' | ||
|
||
const writeAtomic = promisify(fwa) | ||
|
||
/** | ||
* Write a file atomically | ||
*/ | ||
async function writeFile (file: string, contents: Uint8Array): Promise<void> { | ||
try { | ||
await writeAtomic(file, contents) | ||
const writer = new Writer(file) | ||
await writer.write(contents) | ||
} catch (err: any) { | ||
if (err.code === 'EPERM' && err.syscall === 'rename') { | ||
// fast-write-atomic writes a file to a temp location before renaming it. | ||
// On Windows, if the final file already exists this error is thrown. | ||
// No such error is thrown on Linux/Mac | ||
if (err.syscall === 'rename' && ['ENOENT', 'EPERM'].includes(err.code)) { | ||
// steno writes a file to a temp location before renaming it. | ||
// If the final file already exists this error is thrown. | ||
// Make sure we can read & write to this file | ||
await fs.access(file, fs.constants.F_OK | fs.constants.W_OK) | ||
|
||
// The file was created by another context - this means there were | ||
// attempts to write the same block by two different function calls | ||
return | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should add the newline back here. |
||
|
||
throw err | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { CID } from 'multiformats/cid' | ||
import { expose } from 'threads/worker' | ||
import { FsBlockstore } from '../../src/index.js' | ||
|
||
let fs: FsBlockstore | ||
expose({ | ||
async isReady (path) { | ||
fs = new FsBlockstore(path) | ||
try { | ||
await fs.open() | ||
return true | ||
} catch (err) { | ||
// eslint-disable-next-line no-console | ||
console.error('Error opening blockstore', err) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so we can get some error information in testing environment without dealing with logger? i can add in a logger if desired. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
throw err | ||
} | ||
}, | ||
async put (cidString, value) { | ||
const key = CID.parse(cidString) | ||
try { | ||
return await fs.put(key, value) | ||
} catch (err) { | ||
// eslint-disable-next-line no-console | ||
console.error('Error putting block', err) | ||
throw err | ||
} | ||
} | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,31 +14,27 @@ | |
|
||
import fs from 'node:fs/promises' | ||
import path from 'node:path' | ||
import { promisify } from 'util' | ||
import { BaseDatastore } from 'datastore-core' | ||
// @ts-expect-error no types | ||
import fwa from 'fast-write-atomic' | ||
import { Key } from 'interface-datastore' | ||
import { OpenFailedError, NotFoundError, PutFailedError, DeleteFailedError } from 'interface-store' | ||
import glob from 'it-glob' | ||
import map from 'it-map' | ||
import parallel from 'it-parallel-batch' | ||
import { Writer } from 'steno' | ||
import type { KeyQuery, Pair, Query } from 'interface-datastore' | ||
import type { AwaitIterable } from 'interface-store' | ||
|
||
const writeAtomic = promisify(fwa) | ||
|
||
/** | ||
* Write a file atomically | ||
*/ | ||
async function writeFile (path: string, contents: Uint8Array): Promise<void> { | ||
try { | ||
await writeAtomic(path, contents) | ||
const writer = new Writer(path) | ||
await writer.write(contents) | ||
} catch (err: any) { | ||
if (err.code === 'EPERM' && err.syscall === 'rename') { | ||
// fast-write-atomic writes a file to a temp location before renaming it. | ||
// On Windows, if the final file already exists this error is thrown. | ||
// No such error is thrown on Linux/Mac | ||
if (err.syscall === 'rename' && ['ENOENT', 'EPERM'].includes(err.code)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see comment from same changes in blockstore-fs: #305 (comment) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looks like we're also getting an EPERM on 'open' with fast-atomic-write on windows:
https://github.com/ipfs/js-stores/actions/runs/8757918340/job/24047367236?pr=305#step:5:687 This is not failing on windows with steno: #285 |
||
// steno writes a file to a temp location before renaming it. | ||
// If the final file already exists this error is thrown. | ||
// Make sure we can read & write to this file | ||
await fs.access(path, fs.constants.F_OK | fs.constants.W_OK) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { Key } from 'interface-datastore' | ||
import { expose } from 'threads/worker' | ||
import { FsDatastore } from '../../src/index.js' | ||
|
||
let fs: FsDatastore | ||
expose({ | ||
async isReady (path) { | ||
fs = new FsDatastore(path) | ||
try { | ||
await fs.open() | ||
return true | ||
} catch (err) { | ||
// eslint-disable-next-line no-console | ||
console.error('Error opening blockstore', err) | ||
throw err | ||
} | ||
}, | ||
async put (keyString, value) { | ||
const key = new Key(keyString) | ||
try { | ||
return await fs.put(key, value) | ||
} catch (err) { | ||
// eslint-disable-next-line no-console | ||
console.error('Error putting block', err) | ||
SgtPooki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
throw err | ||
} | ||
} | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI that on my mac, an
ENOENT
Error was firing on therename
call.example:
I think if we validate that the final file exists and is F_OK, W_OK as we were doing for
EPERM
, this is safe.I also thought about checking that
error.dest
matches, but it seems like that is effectively what we're doing withfs.access
, so this should work