fontmin-rs 0.1.1 → 0.3.0

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.
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from 'node:crypto'
2
- import { mkdir, open, readFile, rm, stat } from 'node:fs/promises'
2
+ import { mkdir, open, readFile, readdir, rm, stat } from 'node:fs/promises'
3
3
  import { join } from 'node:path'
4
4
 
5
5
  const CACHE_LOCK_RETRY_COUNT = 200
@@ -8,6 +8,7 @@ const CACHE_LOCK_STALE_MS = 5 * 60_000
8
8
  export async function withCacheLock(cacheRoot, operation) {
9
9
  const lockPath = join(cacheRoot, '.write.lock')
10
10
  const owner = `${process.pid}:${randomUUID()}`
11
+ let recoveredLock = false
11
12
 
12
13
  await mkdir(cacheRoot, { recursive: true })
13
14
 
@@ -24,7 +25,8 @@ export async function withCacheLock(cacheRoot, operation) {
24
25
  const staleOwner = await readStaleLockOwner(lockPath)
25
26
 
26
27
  if (staleOwner !== undefined) {
27
- await removeLockIfOwned(lockPath, staleOwner)
28
+ recoveredLock =
29
+ (await removeLockIfOwned(lockPath, staleOwner)) || recoveredLock
28
30
  continue
29
31
  }
30
32
 
@@ -40,6 +42,16 @@ export async function withCacheLock(cacheRoot, operation) {
40
42
  throw error
41
43
  }
42
44
 
45
+ if (recoveredLock) {
46
+ try {
47
+ await cleanupCacheTemporaryFiles(cacheRoot)
48
+ } catch (error) {
49
+ await lock.close()
50
+ await removeLockIfOwned(lockPath, owner)
51
+ throw error
52
+ }
53
+ }
54
+
43
55
  try {
44
56
  return await operation()
45
57
  } finally {
@@ -58,7 +70,10 @@ async function readStaleLockOwner(path) {
58
70
  stat(path),
59
71
  ])
60
72
 
61
- return Date.now() - lockStat.mtimeMs > CACHE_LOCK_STALE_MS
73
+ const ownerPid = parseOwnerPid(owner)
74
+
75
+ return (ownerPid !== undefined && !isProcessAlive(ownerPid)) ||
76
+ Date.now() - lockStat.mtimeMs > CACHE_LOCK_STALE_MS
62
77
  ? owner
63
78
  : undefined
64
79
  } catch (error) {
@@ -70,6 +85,53 @@ async function readStaleLockOwner(path) {
70
85
  }
71
86
  }
72
87
 
88
+ async function cleanupCacheTemporaryFiles(cacheRoot) {
89
+ await cleanupTemporaryFilesInTree(cacheRoot)
90
+ }
91
+
92
+ async function cleanupTemporaryFilesInTree(path) {
93
+ let entries
94
+
95
+ try {
96
+ entries = await readdir(path, { withFileTypes: true })
97
+ } catch (error) {
98
+ if (hasErrorCode(error, 'ENOENT')) {
99
+ return
100
+ }
101
+
102
+ throw error
103
+ }
104
+
105
+ await Promise.all(
106
+ entries.map(entry => {
107
+ const entryPath = join(path, entry.name)
108
+
109
+ if (entry.isDirectory()) {
110
+ return cleanupTemporaryFilesInTree(entryPath)
111
+ }
112
+
113
+ return entry.isFile() && entry.name.endsWith('.tmp')
114
+ ? rm(entryPath, { force: true })
115
+ : Promise.resolve()
116
+ }),
117
+ )
118
+ }
119
+
120
+ function parseOwnerPid(owner) {
121
+ const pid = Math.trunc(Number(owner.split(':', 1)[0] ?? ''))
122
+
123
+ return Number.isSafeInteger(pid) && pid > 0 ? pid : undefined
124
+ }
125
+
126
+ function isProcessAlive(pid) {
127
+ try {
128
+ process.kill(pid, 0)
129
+ return true
130
+ } catch (error) {
131
+ return !hasErrorCode(error, 'ESRCH')
132
+ }
133
+ }
134
+
73
135
  async function removeLockIfOwned(path, owner) {
74
136
  try {
75
137
  if ((await readFile(path, 'utf8')) !== owner) {