@nxtedition/lib 28.0.18 → 28.0.21

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.
Files changed (5) hide show
  1. package/app.js +1 -1
  2. package/cache.js +20 -4
  3. package/http.js +121 -8
  4. package/memory.js +10 -2
  5. package/package.json +3 -2
package/app.js CHANGED
@@ -810,7 +810,7 @@ export function makeApp(appConfig, onTerminateOrMeta, metaOrNull) {
810
810
  messages.push({
811
811
  id: 'app:container_memory_usage',
812
812
  level: usagePercent > 90 ? 50 : usagePercent > 70 ? 40 : 30,
813
- msg: `Memory Usage: ${usagePercent.toFixed(2)}%`,
813
+ msg: `Memory Usage: ${usagePercent.toFixed(2)}% (${(memory.containerUsage / 1e9).toFixed(2)} GiB / ${(memory.containerLimit / 1e9).toFixed(2)} GiB)`,
814
814
  })
815
815
  }
816
816
 
package/cache.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { DatabaseSync } from 'node:sqlite'
2
2
  import { LRUCache } from 'lru-cache'
3
3
  import { fastNow } from './time.js'
4
+ import { doYield } from '@nxtedition/yield'
4
5
 
5
6
  function noop() {}
6
7
 
@@ -50,6 +51,8 @@ export class AsyncCache {
50
51
  #delQuery
51
52
  #purgeStaleQuery
52
53
 
54
+ #setQueue = []
55
+
53
56
  /**
54
57
  * @param {string} location
55
58
  * @param {((...args: any[]) => Promise<V>)|undefined} [valueSelector]
@@ -304,10 +307,23 @@ export class AsyncCache {
304
307
 
305
308
  this.#lru?.set(key, { ttl, stale, value })
306
309
 
307
- try {
308
- this.#setQuery?.run(key, JSON.stringify(value), ttl, stale)
309
- } catch {
310
- // Do nothing...
310
+ this.#setQueue.push({ key, value, ttl, stale })
311
+ if (this.#setQueue.length === 1) {
312
+ doYield(this.#flushSetQueue)
313
+ }
314
+ }
315
+
316
+ #flushSetQueue = () => {
317
+ for (const { key, value, ttl, stale } of this.#setQueue.splice(0, 64)) {
318
+ try {
319
+ this.#setQuery?.run(key, JSON.stringify(value), ttl, stale)
320
+ } catch {
321
+ // Do nothing...
322
+ }
323
+ }
324
+
325
+ if (this.#setQueue.length > 0) {
326
+ doYield(this.#flushSetQueue)
311
327
  }
312
328
  }
313
329
 
package/http.js CHANGED
@@ -216,6 +216,10 @@ export async function requestMiddleware(ctx, next) {
216
216
  stats.pending++
217
217
  }
218
218
 
219
+ if (ctx[kPendingIndex] !== -1) {
220
+ throw new Error('context is already pending')
221
+ }
222
+
219
223
  ctx[kPendingIndex] = pending.push(ctx) - 1
220
224
  try {
221
225
  const isHealthcheck = req.url === '/healthcheck' || req.url === '/_up'
@@ -366,11 +370,12 @@ export async function requestMiddleware(ctx, next) {
366
370
 
367
371
  if (ctx[kPendingIndex] !== -1) {
368
372
  const idx = ctx[kPendingIndex]
373
+ ctx[kPendingIndex] = -1
374
+
369
375
  const tmp = pending.pop()
370
376
  if (tmp !== ctx) {
371
377
  pending[idx] = tmp
372
378
  pending[idx][kPendingIndex] = idx
373
- tmp[kPendingIndex] = -1
374
379
  }
375
380
  }
376
381
 
@@ -464,10 +469,12 @@ export class ServerResponse extends http.ServerResponse {
464
469
  return this.#bytesWritten
465
470
  }
466
471
 
472
+ /** @deprecated */
467
473
  setHeaders() {
468
474
  throw new Error('not supported')
469
475
  }
470
476
 
477
+ /** @deprecated */
471
478
  appendHeader() {
472
479
  throw new Error('not supported')
473
480
  }
@@ -530,7 +537,6 @@ export class ServerResponse extends http.ServerResponse {
530
537
  /**
531
538
  * @param {number} statusCode
532
539
  * @param {Record<string, string>} [headers]
533
- * @returns
534
540
  */
535
541
  writeHead(statusCode, headers) {
536
542
  if (this.#headersSent) {
@@ -554,7 +560,6 @@ export class ServerResponse extends http.ServerResponse {
554
560
 
555
561
  /**
556
562
  * @param {net.Socket} socket
557
- * @returns
558
563
  */
559
564
  assignSocket(socket) {
560
565
  if (!this.destroyed) {
@@ -565,6 +570,11 @@ export class ServerResponse extends http.ServerResponse {
565
570
  return super.assignSocket(socket)
566
571
  }
567
572
 
573
+ /**
574
+ * @param {Buffer|string} chunk
575
+ * @param {string} [encoding]
576
+ * @param {(err: Error) => void} [callback]
577
+ */
568
578
  write(chunk, encoding, callback) {
569
579
  if (!this.destroyed) {
570
580
  if (this.#data === -1) {
@@ -587,6 +597,11 @@ export class ServerResponse extends http.ServerResponse {
587
597
  return super.write(chunk, encoding, callback)
588
598
  }
589
599
 
600
+ /**
601
+ * @param {Buffer|string} chunk
602
+ * @param {string} [encoding]
603
+ * @param {(err: Error) => void} [callback]
604
+ */
590
605
  end(chunk, encoding, callback) {
591
606
  if (!this.destroyed) {
592
607
  this.#end = performance.now() - this.#created
@@ -612,9 +627,7 @@ export class ServerResponse extends http.ServerResponse {
612
627
  }
613
628
 
614
629
  /**
615
- *
616
630
  * @param {Error} [err]
617
- * @returns
618
631
  */
619
632
  destroy(err) {
620
633
  if (!this.destroyed) {
@@ -672,11 +685,14 @@ export class Http2ServerRequest extends http2.Http2ServerRequest {
672
685
  export class Http2ServerResponse extends http2.Http2ServerResponse {
673
686
  #created = performance.now()
674
687
  #bytesWritten = 0
675
- #connect = -1
688
+ #connect = 0
676
689
  #headers = -1
677
690
  #data = -1
678
691
  #end = -1
679
692
 
693
+ #headersObj = kEmptyObj
694
+ #headersSent = false
695
+
680
696
  /**
681
697
  * @returns {{
682
698
  * created: number,
@@ -696,19 +712,108 @@ export class Http2ServerResponse extends http2.Http2ServerResponse {
696
712
  }
697
713
  }
698
714
 
715
+ /**
716
+ * @returns {number}
717
+ */
699
718
  get bytesWritten() {
700
719
  return this.#bytesWritten
701
720
  }
702
721
 
703
- flushHeaders() {
722
+ /** @deprecated */
723
+ setHeaders() {
724
+ throw new Error('not supported')
725
+ }
726
+
727
+ /** @deprecated */
728
+ appendHeader() {
729
+ throw new Error('not supported')
730
+ }
731
+
732
+ /**
733
+ * @param {string} key
734
+ * @param {string} val
735
+ */
736
+ setHeader(key, val) {
737
+ if (this.#headersSent) {
738
+ throw new Error('headers already sent')
739
+ }
740
+
741
+ if (this.#headersObj === kEmptyObj) {
742
+ this.#headersObj = {}
743
+ }
744
+
745
+ this.#headersObj[key] = val === undefined ? undefined : `${val}`
746
+ }
747
+
748
+ /**
749
+ * @param {string} key
750
+ * @param {string} val
751
+ */
752
+ removeHeader(key, val) {
753
+ if (this.#headersSent) {
754
+ throw new Error('headers already sent')
755
+ }
756
+
757
+ if (this.#headersObj !== kEmptyObj) {
758
+ delete this.#headersObj[key]
759
+ }
760
+ }
761
+
762
+ /**
763
+ * @returns {string[]}
764
+ */
765
+ getHeaderNames() {
766
+ if (this.#headersSent) {
767
+ throw new Error('headers already sent')
768
+ }
769
+
770
+ return this.#headersObj === kEmptyObj ? kEmptyArr : Object.keys(this.#headersObj)
771
+ }
772
+
773
+ /**
774
+ * @returns {Record<string, string>}
775
+ */
776
+ getHeaders() {
777
+ return this.#headersObj
778
+ }
779
+
780
+ /**
781
+ * @returns {boolean}
782
+ */
783
+ get headersSent() {
784
+ return this.#headersSent
785
+ }
786
+
787
+ /**
788
+ * @param {number} statusCode
789
+ * @param {Record<string, string>} [headers]
790
+ * @returns
791
+ */
792
+ writeHead(statusCode, headers) {
793
+ if (this.#headersSent) {
794
+ throw new Error('headers already sent')
795
+ }
796
+
797
+ if (this.#headersObj !== kEmptyObj) {
798
+ headers = headers ? Object.assign(this.#headersObj, headers) : this.#headersObj
799
+ }
800
+
704
801
  if (!this.destroyed) {
705
802
  if (this.#headers === -1) {
706
803
  this.#headers = performance.now() - this.#created
707
804
  }
708
805
  }
709
- return super.flushHeaders()
806
+
807
+ this.#headersSent = true
808
+
809
+ return super.writeHead(statusCode, headers)
710
810
  }
711
811
 
812
+ /**
813
+ * @param {Buffer|string} chunk
814
+ * @param {string} [encoding]
815
+ * @param {(err: Error) => void} [callback]
816
+ */
712
817
  write(chunk, encoding, callback) {
713
818
  if (!this.destroyed) {
714
819
  if (this.#data === -1) {
@@ -731,6 +836,11 @@ export class Http2ServerResponse extends http2.Http2ServerResponse {
731
836
  return super.write(chunk, encoding, callback)
732
837
  }
733
838
 
839
+ /**
840
+ * @param {Buffer|string} chunk
841
+ * @param {string} [encoding]
842
+ * @param {(err: Error) => void} [callback]
843
+ */
734
844
  end(chunk, encoding, callback) {
735
845
  if (!this.destroyed) {
736
846
  this.#end = performance.now() - this.#created
@@ -755,6 +865,9 @@ export class Http2ServerResponse extends http2.Http2ServerResponse {
755
865
  return super.end(chunk, encoding, callback)
756
866
  }
757
867
 
868
+ /**
869
+ * @param {Error} [err]
870
+ */
758
871
  destroy(err) {
759
872
  if (!this.destroyed) {
760
873
  if (this.#end === -1) {
package/memory.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { readFileSync } from 'node:fs'
2
2
 
3
+ const INACTIVE_FILE_REGEX = /^inactive_file\s+(\d+)/m
4
+
3
5
  function readFile(path) {
4
6
  try {
5
7
  return readFileSync(path, 'utf8').trim()
@@ -30,13 +32,19 @@ export function getContainerMemoryUsage() {
30
32
  // cgroups v2
31
33
  const v2Usage = readFile('/sys/fs/cgroup/memory.current')
32
34
  if (v2Usage) {
33
- return Number(v2Usage)
35
+ const usage = Number(v2Usage)
36
+ const stat = readFile('/sys/fs/cgroup/memory.stat')
37
+ const inactiveFile = Number(stat?.match(INACTIVE_FILE_REGEX)?.[1] ?? 0)
38
+ return Math.max(0, usage - inactiveFile)
34
39
  }
35
40
 
36
41
  // cgroups v1
37
42
  const v1Usage = readFile('/sys/fs/cgroup/memory/memory.usage_in_bytes')
38
43
  if (v1Usage) {
39
- return Number(v1Usage)
44
+ const usage = Number(v1Usage)
45
+ const stat = readFile('/sys/fs/cgroup/memory/memory.stat')
46
+ const inactiveFile = Number(stat?.match(INACTIVE_FILE_REGEX)?.[1] ?? 0)
47
+ return Math.max(0, usage - inactiveFile)
40
48
  }
41
49
 
42
50
  return undefined
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/lib",
3
- "version": "28.0.18",
3
+ "version": "28.0.21",
4
4
  "license": "UNLICENSED",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "type": "module",
@@ -54,6 +54,7 @@
54
54
  "@nxtedition/sched": "^1.0.2",
55
55
  "@nxtedition/template": "^1.0.10",
56
56
  "@nxtedition/weak-cache": "^1.0.2",
57
+ "@nxtedition/yield": "^1.0.2",
57
58
  "diff": "5.2.0",
58
59
  "fast-querystring": "^1.1.2",
59
60
  "flamegraph-middleware": "^1.0.0",
@@ -92,5 +93,5 @@
92
93
  "pino": ">=7.0.0",
93
94
  "rxjs": "^7.0.0"
94
95
  },
95
- "gitHead": "0e9eba55a86700d53a902b3478366d6cb9791634"
96
+ "gitHead": "d6448b2a05077952c84d8fb8bcbcbf0c2d78fbd5"
96
97
  }