| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 9c7e664 commit 67fb765
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -16,7 +16,14 @@ const filename = path.resolve(tmpdir.path, | |||
| 16 | 16 | const bench = common.createBenchmark(main, { | |
| 17 | 17 | duration: [5], | |
| 18 | 18 | encoding: ['', 'utf-8'], | |
| 19 | - len: [1024, 16 * 1024 * 1024], | ||
| 19 | + len: [ | ||
| 20 | + 1024, | ||
| 21 | + 512 * 1024, | ||
| 22 | + 4 * 1024 ** 2, | ||
| 23 | + 8 * 1024 ** 2, | ||
| 24 | + 16 * 1024 ** 2, | ||
| 25 | + 32 * 1024 ** 2, | ||
| 26 | + ], | ||
| 20 | 27 | concurrent: [1, 10] | |
| 21 | 28 | }); | |
| 22 | 29 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -339,6 +339,9 @@ function readFileAfterStat(err, stats) { | |||
| 339 | 339 | if (err) | |
| 340 | 340 | return context.close(err); | |
| 341 | 341 | ||
| 342 | + // TODO(BridgeAR): Check if allocating a smaller chunk is better performance | ||
| 343 | + // wise, similar to the promise based version (less peak memory and chunked | ||
| 344 | + // stringify operations vs multiple C++/JS boundary crossings). | ||
| 342 | 345 | const size = context.size = isFileType(stats, S_IFREG) ? stats[8] : 0; | |
| 343 | 346 | ||
| 344 | 347 | if (size > kIoMaxLength) { | |
@@ -348,6 +351,8 @@ function readFileAfterStat(err, stats) { | |||
| 348 | 351 | ||
| 349 | 352 | try { | |
| 350 | 353 | if (size === 0) { | |
| 354 | + // TODO(BridgeAR): If an encoding is set, use the StringDecoder to concat | ||
| 355 | + // the result and reuse the buffer instead of allocating a new one. | ||
| 351 | 356 | context.buffers = []; | |
| 352 | 357 | } else { | |
| 353 | 358 | context.buffer = Buffer.allocUnsafeSlow(size); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -86,6 +86,7 @@ const { | |||
| 86 | 86 | promisify, | |
| 87 | 87 | } = require('internal/util'); | |
| 88 | 88 | const { EventEmitterMixin } = require('internal/event_target'); | |
| 89 | + const { StringDecoder } = require('string_decoder'); | ||
| 89 | 90 | const { watch } = require('internal/fs/watchers'); | |
| 90 | 91 | const { isIterable } = require('internal/streams/utils'); | |
| 91 | 92 | const assert = require('internal/assert'); | |
@@ -416,63 +417,83 @@ async function writeFileHandle(filehandle, data, signal, encoding) { | |||
| 416 | 417 | ||
| 417 | 418 | async function readFileHandle(filehandle, options) { | |
| 418 | 419 | const signal = options?.signal; | |
| 420 | + const encoding = options?.encoding; | ||
| 421 | + const decoder = encoding && new StringDecoder(encoding); | ||
| 419 | 422 | ||
| 420 | 423 | checkAborted(signal); | |
| 421 | 424 | ||
| 422 | 425 | const statFields = await binding.fstat(filehandle.fd, false, kUsePromises); | |
| 423 | 426 | ||
| 424 | 427 | checkAborted(signal); | |
| 425 | 428 | ||
| 426 | - let size; | ||
| 429 | + let size = 0; | ||
| 430 | + let length = 0; | ||
| 427 | 431 | if ((statFields[1/* mode */] & S_IFMT) === S_IFREG) { | |
| 428 | 432 | size = statFields[8/* size */]; | |
| 429 | - } else { | ||
| 430 | - size = 0; | ||
| 433 | + length = encoding ? MathMin(size, kReadFileBufferLength) : size; | ||
| 434 | + } | ||
| 435 | + if (length === 0) { | ||
| 436 | + length = kReadFileUnknownBufferLength; | ||
| 431 | 437 | } | |
| 432 | 438 | ||
| 433 | 439 | if (size > kIoMaxLength) | |
| 434 | 440 | throw new ERR_FS_FILE_TOO_LARGE(size); | |
| 435 | 441 | ||
| 436 | - let endOfFile = false; | ||
| 437 | 442 | let totalRead = 0; | |
| 438 | - const noSize = size === 0; | ||
| 439 | - const buffers = []; | ||
| 440 | - const fullBuffer = noSize ? undefined : Buffer.allocUnsafeSlow(size); | ||
| 441 | - do { | ||
| 443 | + let buffer = Buffer.allocUnsafeSlow(length); | ||
| 444 | + let result = ''; | ||
| 445 | + let offset = 0; | ||
| 446 | + let buffers; | ||
| 447 | + const chunkedRead = length > kReadFileBufferLength; | ||
| 448 | + | ||
| 449 | + while (true) { | ||
| 442 | 450 | checkAborted(signal); | |
| 443 | - let buffer; | ||
| 444 | - let offset; | ||
| 445 | - let length; | ||
| 446 | - if (noSize) { | ||
| 447 | - buffer = Buffer.allocUnsafeSlow(kReadFileUnknownBufferLength); | ||
| 448 | - offset = 0; | ||
| 449 | - length = kReadFileUnknownBufferLength; | ||
| 450 | - } else { | ||
| 451 | - buffer = fullBuffer; | ||
| 452 | - offset = totalRead; | ||
| 451 | + | ||
| 452 | + if (chunkedRead) { | ||
| 453 | 453 | length = MathMin(size - totalRead, kReadFileBufferLength); | |
| 454 | 454 | } | |
| 455 | 455 | ||
| 456 | 456 | const bytesRead = (await binding.read(filehandle.fd, buffer, offset, | |
| 457 | - length, -1, kUsePromises)) || 0; | ||
| 457 | + length, -1, kUsePromises)) ?? 0; | ||
| 458 | 458 | totalRead += bytesRead; | |
| 459 | - endOfFile = bytesRead === 0 || totalRead === size; | ||
| 460 | - if (noSize && bytesRead > 0) { | ||
| 461 | - const isBufferFull = bytesRead === kReadFileUnknownBufferLength; | ||
| 462 | - const chunkBuffer = isBufferFull ? buffer : buffer.slice(0, bytesRead); | ||
| 463 | - ArrayPrototypePush(buffers, chunkBuffer); | ||
| 459 | + | ||
| 460 | + if (bytesRead === 0 || | ||
| 461 | + totalRead === size || | ||
| 462 | + (bytesRead !== buffer.length && !chunkedRead)) { | ||
| 463 | + const singleRead = bytesRead === totalRead; | ||
| 464 | + | ||
| 465 | + const bytesToCheck = chunkedRead ? totalRead : bytesRead; | ||
| 466 | + | ||
| 467 | + if (bytesToCheck !== buffer.length) { | ||
| 468 | + buffer = buffer.subarray(0, bytesToCheck); | ||
| 469 | + } | ||
| 470 | + | ||
| 471 | + if (!encoding) { | ||
| 472 | + if (size === 0 && !singleRead) { | ||
| 473 | + ArrayPrototypePush(buffers, buffer); | ||
| 474 | + return Buffer.concat(buffers, totalRead); | ||
| 475 | + } | ||
| 476 | + return buffer; | ||
| 477 | + } | ||
| 478 | + | ||
| 479 | + if (singleRead) { | ||
| 480 | + return buffer.toString(encoding); | ||
| 481 | + } | ||
| 482 | + result += decoder.end(buffer); | ||
| 483 | + return result; | ||
| 464 | 484 | } | |
| 465 | - } while (!endOfFile); | ||
| 466 | 485 | ||
| 467 | - let result; | ||
| 468 | - if (size > 0) { | ||
| 469 | - result = totalRead === size ? fullBuffer : fullBuffer.slice(0, totalRead); | ||
| 470 | - } else { | ||
| 471 | - result = buffers.length === 1 ? buffers[0] : Buffer.concat(buffers, | ||
| 472 | - totalRead); | ||
| 486 | + if (encoding) { | ||
| 487 | + result += decoder.write(buffer); | ||
| 488 | + } else if (size !== 0) { | ||
| 489 | + offset = totalRead; | ||
| 490 | + } else { | ||
| 491 | + buffers ??= []; | ||
| 492 | + // Unknown file size requires chunks. | ||
| 493 | + ArrayPrototypePush(buffers, buffer); | ||
| 494 | + buffer = Buffer.allocUnsafeSlow(kReadFileUnknownBufferLength); | ||
| 495 | + } | ||
| 473 | 496 | } | |
| 474 | - | ||
| 475 | - return options.encoding ? result.toString(options.encoding) : result; | ||
| 476 | 497 | } | |
| 477 | 498 | ||
| 478 | 499 | // All of the functions are defined as async in order to ensure that errors | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,3 +1,4 @@ | |||
| 1 | + // Flags: --expose-internals | ||
| 1 | 2 | 'use strict'; | |
| 2 | 3 | ||
| 3 | 4 | const common = require('../common'); | |
@@ -6,15 +7,15 @@ const assert = require('assert'); | |||
| 6 | 7 | const path = require('path'); | |
| 7 | 8 | const { writeFile, readFile } = require('fs').promises; | |
| 8 | 9 | const tmpdir = require('../common/tmpdir'); | |
| 10 | + const { internalBinding } = require('internal/test/binding'); | ||
| 11 | + const fsBinding = internalBinding('fs'); | ||
| 9 | 12 | tmpdir.refresh(); | |
| 10 | 13 | ||
| 11 | 14 | const fn = path.join(tmpdir.path, 'large-file'); | |
| 12 | 15 | ||
| 13 | 16 | // Creating large buffer with random content | |
| 14 | 17 | const largeBuffer = Buffer.from( | |
| 15 | - Array.apply(null, { length: 16834 * 2 }) | ||
| 16 | - .map(Math.random) | ||
| 17 | - .map((number) => (number * (1 << 8))) | ||
| 18 | + Array.from({ length: 1024 ** 2 + 19 }, (_, index) => index) | ||
| 18 | 19 | ); | |
| 19 | 20 | ||
| 20 | 21 | async function createLargeFile() { | |
@@ -69,11 +70,22 @@ async function validateWrongSignalParam() { | |||
| 69 | 70 | ||
| 70 | 71 | } | |
| 71 | 72 | ||
| 73 | + async function validateZeroByteLiar() { | ||
| 74 | + const originalFStat = fsBinding.fstat; | ||
| 75 | + fsBinding.fstat = common.mustCall( | ||
| 76 | + () => (/* stat fields */ [0, 1, 2, 3, 4, 5, 6, 7, 0 /* size */]) | ||
| 77 | + ); | ||
| 78 | + const readBuffer = await readFile(fn); | ||
| 79 | + assert.strictEqual(readBuffer.toString(), largeBuffer.toString()); | ||
| 80 | + fsBinding.fstat = originalFStat; | ||
| 81 | + } | ||
| 82 | + | ||
| 72 | 83 | (async () => { | |
| 73 | 84 | await createLargeFile(); | |
| 74 | 85 | await validateReadFile(); | |
| 75 | 86 | await validateReadFileProc(); | |
| 76 | 87 | await validateReadFileAbortLogicBefore(); | |
| 77 | 88 | await validateReadFileAbortLogicDuring(); | |
| 78 | 89 | await validateWrongSignalParam(); | |
| 90 | + await validateZeroByteLiar(); | ||
| 79 | 91 | })().then(common.mustCall()); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments