@deepseek-ai/dsh-atomic-write 0.1.6-alpha.1 → 0.1.6-alpha.2

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.
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/util/atomic-write/README.md
5
- README.md: c3ce840f79c38fb7fac404786f0e5a7e41138d57
6
- README.zh.md: 9cc1f271f70e113637b88b7e3984d431e84c647f
5
+ README.md: 89770a6d318da778e9dcb7a462dc7754244a0e41
6
+ README.zh.md: a64771ae1266f132254c82aaa71f193dbf33cd06
package/README.md CHANGED
@@ -58,6 +58,8 @@ Only writers contend — readers never take the lock — and a contender backs o
58
58
 
59
59
  ### Failures to plan for
60
60
 
61
+ Windows retries one `EPERM` when the lock cannot be observed, because its holder can release between exclusive creation and the existence check. A repeated unconfirmed `EPERM` is rethrown without running the operation.
62
+
61
63
  The lock's parent directory must already exist, so `withFileLock` rejects an invalid parent hierarchy before running the operation. A process that exits while holding the lock leaves the lock sibling behind; later writers time out, and an operator removes it only after verifying that no writer still owns it.
62
64
 
63
65
  -----
package/README.zh.md CHANGED
@@ -58,6 +58,8 @@ await withFileLock('/home/u/.dsh/settings.yaml', async () => {
58
58
 
59
59
  ### 需要规划的失败
60
60
 
61
+ Windows 在无法观察到锁时会对 `EPERM` 重试一次,因为持锁方可能在独占创建与存在性检查之间释放锁。再次出现无法确认锁存在的 `EPERM` 时,会重新抛出错误且不运行操作。
62
+
61
63
  锁的父目录必须已经存在,因此 `withFileLock` 会在运行操作之前拒绝无效的父目录层级。持锁进程退出时会把锁文件留在原地;后续写入方超时失败,操作者只有在确认没有写入方仍持有该锁后才会移除它。
62
64
 
63
65
  -----
package/lib/index.js CHANGED
@@ -109,8 +109,9 @@ const DEFAULT_LOCK_WAIT_MS = 2e3;
109
109
  * rename-based commit of {@link writeFileAtomic}, readers stay lock-free and
110
110
  * only writers contend. `EEXIST` is contention directly; an `EPERM` is
111
111
  * contention only when a fresh `lstat` confirms the lock path exists, covering
112
- * Windows exclusive-create behavior without hiding an unrelated permission
113
- * failure. Contention backs off exponentially and fails with a timed-out error
112
+ * Windows exclusive-create behavior. Windows retries one unconfirmed EPERM
113
+ * because the holder can release before the probe; a repeated unconfirmed
114
+ * permission error is rethrown. Contention backs off exponentially and times out
114
115
  * after the deadline. The contender never removes an existing lock because
115
116
  * file age cannot prove that its owner stopped; orphan recovery is an operator
116
117
  * action. The parent directory must exist.
@@ -123,6 +124,7 @@ async function withFileLock(filename, operation, options) {
123
124
  const lockPath = `${filename}.lock`;
124
125
  const deadline = Date.now() + (options?.waitMs ?? DEFAULT_LOCK_WAIT_MS);
125
126
  let delay = LOCK_RETRY_INITIAL_MS;
127
+ let retriedUnconfirmedPermissionError = false;
126
128
  for (;;) {
127
129
  try {
128
130
  await writeFile(lockPath, `${process.pid}\n`, {
@@ -131,7 +133,10 @@ async function withFileLock(filename, operation, options) {
131
133
  });
132
134
  break;
133
135
  } catch (error) {
134
- if (!await isLockContention(error, lockPath)) throw error;
136
+ if (!await isLockContention(error, lockPath)) {
137
+ if (process.platform !== "win32" || error?.code !== "EPERM" || retriedUnconfirmedPermissionError) throw error;
138
+ retriedUnconfirmedPermissionError = true;
139
+ }
135
140
  }
136
141
  if (Date.now() >= deadline) throw new Error(`atomic-write: timed out waiting for the writer lock at ${lockPath}`);
137
142
  await new Promise((resolve) => setTimeout(resolve, delay));
@@ -61,8 +61,9 @@ export interface FileLockOptions {
61
61
  * rename-based commit of {@link writeFileAtomic}, readers stay lock-free and
62
62
  * only writers contend. `EEXIST` is contention directly; an `EPERM` is
63
63
  * contention only when a fresh `lstat` confirms the lock path exists, covering
64
- * Windows exclusive-create behavior without hiding an unrelated permission
65
- * failure. Contention backs off exponentially and fails with a timed-out error
64
+ * Windows exclusive-create behavior. Windows retries one unconfirmed EPERM
65
+ * because the holder can release before the probe; a repeated unconfirmed
66
+ * permission error is rethrown. Contention backs off exponentially and times out
66
67
  * after the deadline. The contender never removes an existing lock because
67
68
  * file age cannot prove that its owner stopped; orphan recovery is an operator
68
69
  * action. The parent directory must exist.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-atomic-write",
3
3
  "description": "Zero-dependency atomic file replacement: exclusive-create random-suffix temp + rename carrying the caller-stated permissions (writeFileAtomic)",
4
- "version": "0.1.6-alpha.1",
4
+ "version": "0.1.6-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },