Skip to content

Commit 0be1f75

Browse files
authored
fix: storage adapter throw error on stat (#8893) (#8898)
Signed-off-by: Alexander Onnikov <[email protected]>
1 parent 8da0fe8 commit 0be1f75

File tree

6 files changed

+98
-82
lines changed

6 files changed

+98
-82
lines changed

common/config/rush/pnpm-lock.yaml

Lines changed: 11 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/backup/src/backup.ts

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1470,16 +1470,20 @@ async function rebuildSizeInfo (
14701470

14711471
const addFileSize = async (file: string | undefined | null): Promise<void> => {
14721472
if (file != null) {
1473-
const sz = sizeInfo[file]
1474-
const fileSize = sz ?? (await storage.stat(file))
1475-
if (sz === undefined) {
1476-
sizeInfo[file] = fileSize
1477-
processed++
1478-
if (processed % 10 === 0) {
1479-
ctx.info('Calculate size processed', { processed, size: Math.round(result.backupSize / (1024 * 1024)) })
1473+
try {
1474+
const sz = sizeInfo[file]
1475+
const fileSize = sz ?? (await storage.stat(file))
1476+
if (sz === undefined) {
1477+
sizeInfo[file] = fileSize
1478+
processed++
1479+
if (processed % 10 === 0) {
1480+
ctx.info('Calculate size processed', { processed, size: Math.round(result.backupSize / (1024 * 1024)) })
1481+
}
14801482
}
1483+
result.backupSize += fileSize
1484+
} catch (err: any) {
1485+
ctx.error('failed to calculate size', { file, err })
14811486
}
1482-
result.backupSize += fileSize
14831487
}
14841488
}
14851489

@@ -1551,9 +1555,13 @@ export async function backupSize (storage: BackupStorage): Promise<void> {
15511555
console.log('workspace:', backupInfo.workspace ?? '', backupInfo.version)
15521556
const addFileSize = async (file: string | undefined | null): Promise<void> => {
15531557
if (file != null && (await storage.exists(file))) {
1554-
const fileSize = await storage.stat(file)
1555-
console.log(file, fileSize)
1556-
size += fileSize
1558+
try {
1559+
const fileSize = await storage.stat(file)
1560+
console.log(file, fileSize)
1561+
size += fileSize
1562+
} catch (err: any) {
1563+
console.error('failed to calculate size', { file, err })
1564+
}
15571565
}
15581566
}
15591567

@@ -1606,20 +1614,24 @@ export async function backupDownload (storage: BackupStorage, storeIn: string):
16061614
const serverSize: number | undefined = sizeInfo[file]
16071615

16081616
if (!existsSync(target) || force || (serverSize !== undefined && serverSize !== statSync(target).size)) {
1609-
const fileSize = serverSize ?? (await storage.stat(file))
1610-
console.log('downloading', file, fileSize)
1611-
const readStream = await storage.load(file)
1612-
const outp = createWriteStream(target)
1617+
try {
1618+
const fileSize = serverSize ?? (await storage.stat(file))
1619+
console.log('downloading', file, fileSize)
1620+
const readStream = await storage.load(file)
1621+
const outp = createWriteStream(target)
16131622

1614-
readStream.pipe(outp)
1615-
await new Promise<void>((resolve) => {
1616-
readStream.on('end', () => {
1617-
readStream.destroy()
1618-
outp.close()
1619-
resolve()
1623+
readStream.pipe(outp)
1624+
await new Promise<void>((resolve) => {
1625+
readStream.on('end', () => {
1626+
readStream.destroy()
1627+
outp.close()
1628+
resolve()
1629+
})
16201630
})
1621-
})
1622-
size += fileSize
1631+
size += fileSize
1632+
} catch (err: any) {
1633+
console.error('failed to calculate size', { file, err })
1634+
}
16231635
} else {
16241636
console.log('file-same', file)
16251637
}

server/datalake/src/index.ts

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,37 @@ export interface DatalakeConfig extends StorageConfig {
4646
/**
4747
* @public
4848
*/
49-
export function createDatalakeClient (opt: DatalakeConfig, token: string): DatalakeClient {
50-
const endpoint = Number.isInteger(opt.port) ? `${opt.endpoint}:${opt.port}` : opt.endpoint
49+
export function createDatalakeClient (cfg: DatalakeConfig, token: string): DatalakeClient {
50+
const endpoint = Number.isInteger(cfg.port) ? `${cfg.endpoint}:${cfg.port}` : cfg.endpoint
5151
return new DatalakeClient(endpoint, token)
5252
}
5353

5454
export const CONFIG_KIND = 'datalake'
5555

56+
/**
57+
* @public
58+
*/
59+
export interface DatalakeClientOptions {
60+
retryCount?: number
61+
retryInterval?: number
62+
}
63+
5664
/**
5765
* @public
5866
*/
5967
export class DatalakeService implements StorageAdapter {
6068
private readonly client: DatalakeClient
69+
private readonly retryCount: number
70+
private readonly retryInterval: number
6171

62-
constructor (readonly opt: DatalakeConfig) {
72+
constructor (
73+
readonly cfg: DatalakeConfig,
74+
readonly options: DatalakeClientOptions = {}
75+
) {
6376
const token = generateToken(systemAccountEmail, { name: '' }, { service: 'datalake' })
64-
this.client = createDatalakeClient(opt, token)
77+
this.client = createDatalakeClient(cfg, token)
78+
this.retryCount = options.retryCount ?? 5
79+
this.retryInterval = options.retryInterval ?? 50
6580
}
6681

6782
async initialize (ctx: MeasureContext, workspaceId: WorkspaceId): Promise<void> {}
@@ -86,7 +101,7 @@ export class DatalakeService implements StorageAdapter {
86101
async remove (ctx: MeasureContext, workspaceId: WorkspaceId, objectNames: string[]): Promise<void> {
87102
await Promise.all(
88103
objectNames.map(async (objectName) => {
89-
await this.client.deleteObject(ctx, workspaceId, objectName)
104+
await this.retry(ctx, () => this.client.deleteObject(ctx, workspaceId, objectName))
90105
})
91106
)
92107
}
@@ -106,7 +121,7 @@ export class DatalakeService implements StorageAdapter {
106121
next: async () => {
107122
try {
108123
while (hasMore && buffer.length < 50) {
109-
const res = await this.client.listObjects(ctx, workspaceId, cursor)
124+
const res = await this.retry(ctx, () => this.client.listObjects(ctx, workspaceId, cursor))
110125
hasMore = res.cursor !== undefined
111126
cursor = res.cursor
112127

@@ -116,7 +131,7 @@ export class DatalakeService implements StorageAdapter {
116131
_class: core.class.Blob,
117132
etag: blob.etag,
118133
size: (typeof blob.size === 'string' ? parseInt(blob.size) : blob.size) ?? 0,
119-
provider: this.opt.name,
134+
provider: this.cfg.name,
120135
space: core.space.Configuration,
121136
modifiedBy: core.account.ConfigUser,
122137
modifiedOn: 0
@@ -134,32 +149,26 @@ export class DatalakeService implements StorageAdapter {
134149

135150
@withContext('stat')
136151
async stat (ctx: MeasureContext, workspaceId: WorkspaceId, objectName: string): Promise<Blob | undefined> {
137-
return await withRetry(ctx, 5, async () => {
138-
try {
139-
const result = await this.client.statObject(ctx, workspaceId, objectName)
140-
if (result !== undefined) {
141-
return {
142-
provider: '',
143-
_class: core.class.Blob,
144-
_id: objectName as Ref<Blob>,
145-
contentType: result.type,
146-
size: result.size ?? 0,
147-
etag: result.etag ?? '',
148-
space: core.space.Configuration,
149-
modifiedBy: core.account.System,
150-
modifiedOn: result.lastModified,
151-
version: null
152-
}
153-
}
154-
} catch (err) {
155-
ctx.error('failed to stat object', { error: err, objectName, workspaceId: workspaceId.name })
152+
const result = await this.retry(ctx, () => this.client.statObject(ctx, workspaceId, objectName))
153+
if (result !== undefined) {
154+
return {
155+
provider: '',
156+
_class: core.class.Blob,
157+
_id: objectName as Ref<Blob>,
158+
contentType: result.type,
159+
size: result.size ?? 0,
160+
etag: result.etag ?? '',
161+
space: core.space.Configuration,
162+
modifiedBy: core.account.System,
163+
modifiedOn: result.lastModified,
164+
version: null
156165
}
157-
})
166+
}
158167
}
159168

160169
@withContext('get')
161170
async get (ctx: MeasureContext, workspaceId: WorkspaceId, objectName: string): Promise<Readable> {
162-
return await this.client.getObject(ctx, workspaceId, objectName)
171+
return await this.retry(ctx, () => this.client.getObject(ctx, workspaceId, objectName))
163172
}
164173

165174
@withContext('put')
@@ -178,7 +187,7 @@ export class DatalakeService implements StorageAdapter {
178187
}
179188

180189
const { etag } = await ctx.with('put', {}, (ctx) =>
181-
withRetry(ctx, 5, () => this.client.putObject(ctx, workspaceId, objectName, stream, params))
190+
this.retry(ctx, () => this.client.putObject(ctx, workspaceId, objectName, stream, params))
182191
)
183192

184193
return {
@@ -189,7 +198,7 @@ export class DatalakeService implements StorageAdapter {
189198

190199
@withContext('read')
191200
async read (ctx: MeasureContext, workspaceId: WorkspaceId, objectName: string): Promise<Buffer[]> {
192-
const data = await this.client.getObject(ctx, workspaceId, objectName)
201+
const data = await this.retry(ctx, () => this.client.getObject(ctx, workspaceId, objectName))
193202
const chunks: Buffer[] = []
194203

195204
for await (const chunk of data) {
@@ -207,12 +216,16 @@ export class DatalakeService implements StorageAdapter {
207216
offset: number,
208217
length?: number
209218
): Promise<Readable> {
210-
return await this.client.getPartialObject(ctx, workspaceId, objectName, offset, length)
219+
return await this.retry(ctx, () => this.client.getPartialObject(ctx, workspaceId, objectName, offset, length))
211220
}
212221

213222
async getUrl (ctx: MeasureContext, workspaceId: WorkspaceId, objectName: string): Promise<string> {
214223
return this.client.getObjectUrl(ctx, workspaceId, objectName)
215224
}
225+
226+
async retry<T>(ctx: MeasureContext, op: () => Promise<T>): Promise<T> {
227+
return await withRetry(ctx, this.retryCount, op, this.retryInterval)
228+
}
216229
}
217230

218231
export function processConfigFromEnv (storageConfig: StorageConfiguration): string | undefined {
@@ -244,7 +257,7 @@ async function withRetry<T> (
244257
} catch (err: any) {
245258
error = err
246259
ctx.error('error', { err })
247-
if (retries !== 0) {
260+
if (retries !== 0 && delay > 0) {
248261
await new Promise((resolve) => setTimeout(resolve, delay))
249262
}
250263
}

server/minio/package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,12 @@
3131
"@types/node": "~20.11.16",
3232
"jest": "^29.7.0",
3333
"ts-jest": "^29.1.1",
34-
"@types/jest": "^29.5.5",
35-
"@types/minio": "~7.0.11"
34+
"@types/jest": "^29.5.5"
3635
},
3736
"dependencies": {
3837
"@hcengineering/core": "^0.6.32",
3938
"@hcengineering/platform": "^0.6.11",
4039
"@hcengineering/server-core": "^0.6.1",
41-
"minio": "^8.0.0"
40+
"minio": "^8.0.5"
4241
}
4342
}

0 commit comments

Comments
 (0)