pinojs / sonic-boom

Extremely fast utf8 only stream implementation
MIT License
266 stars 41 forks source link

fix: ensure writes are always atomic #97

Closed ronag closed 3 years ago

ronag commented 3 years ago

Fixes: https://github.com/pinojs/sonic-boom/issues/96

mcollina commented 3 years ago

why did you close this?

ronag commented 3 years ago

missing tests

ronag commented 3 years ago

There is one problem here. We can still write larger than MAX_WRITE if the source doesn't properly pause given the return value of write. But I suppose that's not a big problem? Otherwise the solution becomes more complicated and slower (_buf would need to be an array of strings).

mcollina commented 3 years ago

We can still write larger than MAX_WRITE if the source doesn't properly pause given the return value of write

This is the case of pino.

ronag commented 3 years ago

So this PR is not sufficient.

ronag commented 3 years ago

@mcollina PTAL, doesn't pass tests but I think this could potentially work.

mcollina commented 3 years ago

Agreed, this is quite nice actually

ronag commented 3 years ago

passes all tests except for retry in flushSync on EAGAIN. The whole flushSync I don't understand how it can work when there is a pending async write...

mcollina commented 3 years ago

I'm getting a lot of failures while running your branch locally.

1..12
# failed 8 of 12 tests
# time=2439.583ms
ronag commented 3 years ago

Should be better now. Sorry about that.

ronag commented 3 years ago

the write enormously large buffers test seems to take a very long time to finish now...

mcollina commented 3 years ago

I can see some slowdown but it seems acceptable to solve this problem:

This branch:

benchSonic*1000: 521.245ms
benchSonicSync*1000: 6.851s
benchSonic4k*1000: 466.171ms
benchSonicSync4k*1000: 336.917ms
benchCore*1000: 4.511s
benchConsole*1000: 9.480s

master:

benchSonic*1000: 498.087ms
benchSonicSync*1000: 6.710s
benchSonic4k*1000: 431.978ms
benchSonicSync4k*1000: 295.552ms
benchCore*1000: 4.516s
benchConsole*1000: 9.521s

We can see if there are a few areas were we could optimize.

mcollina commented 3 years ago

The following fixes the broken test:

diff --git a/index.js b/index.js
index fc19cdf..a3118a6 100644
--- a/index.js
+++ b/index.js
@@ -145,8 +145,8 @@ function SonicBoom (opts) {
         this.emit('error', err)
       }
       return
-    }
-
+    }
+
     this._len -= n
     this._writingBuf = this._writingBuf.slice(n)

@@ -335,12 +335,14 @@ SonicBoom.prototype.flushSync = function () {
   }

   while (this._bufs.length) {
+    const buf = this._bufs.shift()
     try {
-      this._len -= fs.writeSync(this.fd, this._bufs.shift(), 'utf8')
+      this._len -= fs.writeSync(this.fd, buf, 'utf8')
     } catch (err) {
       if (err.code !== 'EAGAIN') {
         throw err
       }
+      this._bufs.unshift(buf)

       sleep(BUSY_WRITE_TIMEOUT)
     }

Essentially the flushSync() implementation did not reinsert the shifted buffer in.

ronag commented 3 years ago

Thanks! Fixed.

ronag commented 3 years ago

https://github.com/pinojs/sonic-boom/pull/102