-
Notifications
You must be signed in to change notification settings - Fork 41
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
.write(Buffer) support #174
Changes from 3 commits
9152450
1ed75f0
99b0470
a694b87
ad82abc
7b1079f
5764633
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 |
---|---|---|
|
@@ -7,6 +7,7 @@ const path = require('path') | |
const sleep = require('atomic-sleep') | ||
|
||
const BUSY_WRITE_TIMEOUT = 100 | ||
const kEmptyBuffer = Buffer.allocUnsafe(0) | ||
|
||
// 16 KB. Don't write more than docker buffer size. | ||
// https://github.com/moby/moby/blob/513ec73831269947d38a644c278ce3cac36783b2/daemon/logger/copier.go#L13 | ||
|
@@ -92,10 +93,11 @@ function SonicBoom (opts) { | |
fd = fd || dest | ||
|
||
this._bufs = [] | ||
this._lens = [] | ||
this._len = 0 | ||
this.fd = -1 | ||
this._writing = false | ||
this._writingBuf = '' | ||
this._writingBuf = kEmptyBuffer | ||
this._ending = false | ||
this._reopening = false | ||
this._asyncDrainScheduled = false | ||
|
@@ -106,6 +108,7 @@ function SonicBoom (opts) { | |
this.maxLength = maxLength || 0 | ||
this.maxWrite = maxWrite || MAX_WRITE | ||
this.sync = sync || false | ||
this.writable = true | ||
this._fsync = fsync || false | ||
this.append = append || false | ||
this.mode = mode | ||
|
@@ -141,7 +144,7 @@ function SonicBoom (opts) { | |
} else { | ||
// Let's give the destination some time to process the chunk. | ||
setTimeout(() => { | ||
fs.write(this.fd, this._writingBuf, 'utf8', this.release) | ||
fs.write(this.fd, this._writingBuf, this.release) | ||
}, BUSY_WRITE_TIMEOUT) | ||
} | ||
} else { | ||
|
@@ -167,20 +170,20 @@ function SonicBoom (opts) { | |
// TODO if we have a multi-byte character in the buffer, we need to | ||
// n might not be the same as this._writingBuf.length, so we might loose | ||
// characters here. The solution to this problem is to use a Buffer for _writingBuf. | ||
this._writingBuf = this._writingBuf.slice(n) | ||
this._writingBuf = this._writingBuf.subarray(n) | ||
|
||
if (this._writingBuf.length) { | ||
if (!this.sync) { | ||
fs.write(this.fd, this._writingBuf, 'utf8', this.release) | ||
fs.write(this.fd, this._writingBuf, this.release) | ||
return | ||
} | ||
|
||
try { | ||
do { | ||
const n = fs.writeSync(this.fd, this._writingBuf, 'utf8') | ||
const n = fs.writeSync(this.fd, this._writingBuf) | ||
this._len -= n | ||
this._writingBuf = this._writingBuf.slice(n) | ||
} while (this._writingBuf) | ||
this._writingBuf = this._writingBuf.subarray(n) | ||
} while (this._writingBuf.length) | ||
} catch (err) { | ||
this.release(err) | ||
return | ||
|
@@ -234,13 +237,27 @@ function emitDrain (sonic) { | |
|
||
inherits(SonicBoom, EventEmitter) | ||
|
||
SonicBoom.prototype.write = function (data) { | ||
function mergeBuf (bufs, len) { | ||
if (bufs.length === 0) { | ||
return kEmptyBuffer | ||
} | ||
|
||
if (bufs.length === 1) { | ||
return bufs[0] | ||
} | ||
|
||
return Buffer.concat(bufs, len) | ||
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. This is the source of the slowdown. We should not merge them, but rather keep them as a list. 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. I've tried having a list here, but tests that expect single flush instead of multiples break. Will work on it further now that there are 2 separate content modes 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. All in all Buffer.concat seem to be cheaper than multiple fs.write as long as it doesn't have to allocate memory outside of the Buffer.poolSize. Will investigate further how that could be avoided 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. tried fs.writev to avoid concat, but that made it slower than writeStream |
||
} | ||
|
||
SonicBoom.prototype.write = function (_data) { | ||
if (this.destroyed) { | ||
throw new Error('SonicBoom destroyed') | ||
} | ||
|
||
const data = Buffer.isBuffer(_data) ? _data : Buffer.from(_data, 'utf8') | ||
const len = this._len + data.length | ||
const bufs = this._bufs | ||
const lens = this._lens | ||
|
||
if (this.maxLength && len > this.maxLength) { | ||
this.emit('drop', data) | ||
|
@@ -249,11 +266,13 @@ SonicBoom.prototype.write = function (data) { | |
|
||
if ( | ||
bufs.length === 0 || | ||
bufs[bufs.length - 1].length + data.length > this.maxWrite | ||
lens[lens.length - 1] + data.length > this.maxWrite | ||
) { | ||
bufs.push('' + data) | ||
bufs.push([data]) | ||
lens.push(data.length) | ||
} else { | ||
bufs[bufs.length - 1] += data | ||
bufs[bufs.length - 1].push(data) | ||
lens[lens.length - 1] += data.length | ||
} | ||
|
||
this._len = len | ||
|
@@ -275,7 +294,8 @@ SonicBoom.prototype.flush = function () { | |
} | ||
|
||
if (this._bufs.length === 0) { | ||
this._bufs.push('') | ||
this._bufs.push([]) | ||
this._lens.push(0) | ||
} | ||
|
||
actualWrite(this) | ||
|
@@ -360,21 +380,22 @@ SonicBoom.prototype.flushSync = function () { | |
} | ||
|
||
if (!this._writing && this._writingBuf.length > 0) { | ||
this._bufs.unshift(this._writingBuf) | ||
this._writingBuf = '' | ||
this._bufs.unshift([this._writingBuf]) | ||
this._writingBuf = kEmptyBuffer | ||
} | ||
|
||
let buf = '' | ||
let buf = kEmptyBuffer | ||
while (this._bufs.length || buf.length) { | ||
if (buf.length <= 0) { | ||
buf = this._bufs[0] | ||
buf = mergeBuf(this._bufs[0], this._lens[0]) | ||
} | ||
try { | ||
const n = fs.writeSync(this.fd, buf, 'utf8') | ||
buf = buf.slice(n) | ||
const n = fs.writeSync(this.fd, buf) | ||
buf = buf.subarray(n) | ||
this._len = Math.max(this._len - n, 0) | ||
if (buf.length <= 0) { | ||
this._bufs.shift() | ||
this._lens.shift() | ||
} | ||
} catch (err) { | ||
const shouldRetry = err.code === 'EAGAIN' || err.code === 'EBUSY' | ||
|
@@ -397,17 +418,17 @@ SonicBoom.prototype.destroy = function () { | |
function actualWrite (sonic) { | ||
const release = sonic.release | ||
sonic._writing = true | ||
sonic._writingBuf = sonic._writingBuf || sonic._bufs.shift() || '' | ||
sonic._writingBuf = sonic._writingBuf.length ? sonic._writingBuf : mergeBuf(sonic._bufs.shift(), sonic._lens.shift()) | ||
|
||
if (sonic.sync) { | ||
try { | ||
const written = fs.writeSync(sonic.fd, sonic._writingBuf, 'utf8') | ||
const written = fs.writeSync(sonic.fd, sonic._writingBuf) | ||
release(null, written) | ||
} catch (err) { | ||
release(err) | ||
} | ||
} else { | ||
fs.write(sonic.fd, sonic._writingBuf, 'utf8', release) | ||
fs.write(sonic.fd, sonic._writingBuf, release) | ||
} | ||
} | ||
|
||
|
@@ -419,6 +440,7 @@ function actualClose (sonic) { | |
|
||
sonic.destroyed = true | ||
sonic._bufs = [] | ||
sonic._lens = [] | ||
|
||
if (sonic.fd !== 1 && sonic.fd !== 2) { | ||
fs.close(sonic.fd, done) | ||
|
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.
forgot to mention, this is so that node.js stream util compose function works properly with this