tileserver-gl-light 5.5.0-pre.3 → 5.5.0-pre.5

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.
package/CHANGELOG.md CHANGED
@@ -1,12 +1,13 @@
1
1
  # tileserver-gl changelog
2
2
 
3
- ## 5.5.0-pre.3
3
+ ## 5.5.0-pre.5
4
4
  * Add S3 support for PMTiles with multiple AWS credential profiles (https://github.com/maptiler/tileserver-gl/pull/1779) by @acalcutt
5
5
  * Create .aws directory passthrough folder in Dockerfile (https://github.com/maptiler/tileserver-gl/pull/1784) by @acalcutt
6
6
  * Update eslint to v9 (https://github.com/maptiler/tileserver-gl/pull/1473) by @acalcutt
7
7
  * Fix Renderer Crashes from Failed Fetches (https://github.com/maptiler/tileserver-gl/pull/1798) by @acalcutt
8
8
  * Add Visual Regression Tests for Static Image Overlays (https://github.com/maptiler/tileserver-gl/pull/1792) by @acalcutt
9
9
  * Fix S3 URL parsing for nested paths in AWS buckets (https://github.com/maptiler/tileserver-gl/pull/1819) by @acalcutt
10
+ * Fix Renderer Crashes and Memory Leak (https://github.com/maptiler/tileserver-gl/pull/1825) by @acalcutt
10
11
 
11
12
  ## 5.4.0
12
13
  * Fix the issue where the tile URL cannot be correctly parsed with the HTTPS protocol when using an nginx proxy service (https://github.com/maptiler/tileserver-gl/pull/1578) by @dakanggo
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tileserver-gl-light",
3
- "version": "5.5.0-pre.3",
3
+ "version": "5.5.0-pre.5",
4
4
  "description": "Map tile server for JSON GL styles - serving vector tiles",
5
5
  "main": "src/main.js",
6
6
  "bin": "src/main.js",
@@ -35,7 +35,7 @@
35
35
  "docker": "docker build . && docker run --rm -i -p 8080:8080 $(docker build -q .)"
36
36
  },
37
37
  "dependencies": {
38
- "@aws-sdk/client-s3": "^3.932.0",
38
+ "@aws-sdk/client-s3": "^3.935.0",
39
39
  "@jsse/pbfont": "^0.3.0",
40
40
  "@mapbox/mapbox-gl-rtl-text": "0.3.0",
41
41
  "@mapbox/mbtiles": "0.12.1",
@@ -61,7 +61,7 @@
61
61
  "morgan": "1.10.1",
62
62
  "pbf": "4.0.1",
63
63
  "pmtiles": "4.3.0",
64
- "proj4": "2.20.0",
64
+ "proj4": "2.20.2",
65
65
  "sanitize-filename": "1.6.3",
66
66
  "semver": "^7.7.3",
67
67
  "tileserver-gl-styles": "2.0.0"
@@ -531,30 +531,43 @@ async function respondImage(
531
531
  pool = item.map.renderersStatic[scale];
532
532
  }
533
533
 
534
+ if (!pool) {
535
+ console.error(`Pool not found for scale ${scale}, mode ${mode}`);
536
+ return res.status(500).send('Renderer pool not configured');
537
+ }
538
+
534
539
  pool.acquire(async (err, renderer) => {
535
540
  // Check if pool.acquire failed or returned null/invalid renderer
536
541
  if (err) {
537
542
  console.error('Failed to acquire renderer from pool:', err);
538
- return res.status(503).send('Renderer pool error');
543
+ if (!res.headersSent) {
544
+ return res.status(503).send('Renderer pool error');
545
+ }
546
+ return;
539
547
  }
540
548
 
541
549
  if (!renderer) {
542
550
  console.error(
543
551
  'Renderer is null - likely crashed or failed to initialize',
544
552
  );
545
- return res.status(503).send('Renderer unavailable');
553
+ if (!res.headersSent) {
554
+ return res.status(503).send('Renderer unavailable');
555
+ }
556
+ return;
546
557
  }
547
558
 
548
559
  // Validate renderer has required methods (basic health check)
549
560
  if (typeof renderer.render !== 'function') {
550
561
  console.error('Renderer is invalid - missing render method');
551
- // Destroy the bad renderer and remove from pool
552
562
  try {
553
- pool.destroy(renderer);
563
+ pool.removeBadObject(renderer);
554
564
  } catch (e) {
555
- console.error('Error destroying invalid renderer:', e);
565
+ console.error('Error removing bad renderer:', e);
556
566
  }
557
- return res.status(503).send('Renderer invalid');
567
+ if (!res.headersSent) {
568
+ return res.status(503).send('Renderer invalid');
569
+ }
570
+ return;
558
571
  }
559
572
 
560
573
  // For 512px tiles, use the actual maplibre-native zoom. For 256px tiles, use zoom - 1
@@ -588,13 +601,14 @@ async function respondImage(
588
601
 
589
602
  // Set a timeout for the render operation to detect hung renderers
590
603
  const renderTimeout = setTimeout(() => {
591
- console.error('Renderer timeout - destroying potentially hung renderer');
604
+ console.error('Renderer timeout - destroying hung renderer');
605
+
592
606
  try {
593
- // Don't release back to pool, destroy it
594
- pool.destroy(renderer);
607
+ pool.removeBadObject(renderer);
595
608
  } catch (e) {
596
- console.error('Error destroying timed-out renderer:', e);
609
+ console.error('Error removing timed-out renderer:', e);
597
610
  }
611
+
598
612
  if (!res.headersSent) {
599
613
  res.status(503).send('Renderer timeout');
600
614
  }
@@ -604,13 +618,17 @@ async function respondImage(
604
618
  renderer.render(params, (err, data) => {
605
619
  clearTimeout(renderTimeout);
606
620
 
621
+ if (res.headersSent) {
622
+ // Timeout already fired and sent response, don't process
623
+ return;
624
+ }
625
+
607
626
  if (err) {
608
627
  console.error('Render error:', err);
609
- // Destroy renderer instead of releasing it back to pool since it may be corrupted
610
628
  try {
611
- pool.destroy(renderer);
629
+ pool.removeBadObject(renderer);
612
630
  } catch (e) {
613
- console.error('Error destroying failed renderer:', e);
631
+ console.error('Error removing failed renderer:', e);
614
632
  }
615
633
  if (!res.headersSent) {
616
634
  return res
@@ -646,12 +664,11 @@ async function respondImage(
646
664
  height: height * scale,
647
665
  });
648
666
  }
649
- // HACK(Part 2) 256px tiles are a zoom level lower than maplibre-native default tiles. this hack allows tileserver-gl to support zoom 0 256px tiles, which would actually be zoom -1 in maplibre-native. Since zoom -1 isn't supported, a double sized zoom 0 tile is requested and resized here.
650
667
 
668
+ // HACK(Part 2) 256px tiles are a zoom level lower than maplibre-native default tiles. this hack allows tileserver-gl to support zoom 0 256px tiles, which would actually be zoom -1 in maplibre-native. Since zoom -1 isn't supported, a double sized zoom 0 tile is requested and resized here.
651
669
  if (z === 0 && width === 256) {
652
670
  image.resize(width * scale, height * scale);
653
671
  }
654
- // END HACK(Part 2)
655
672
 
656
673
  const composites = [];
657
674
  if (overlay) {
@@ -659,7 +676,6 @@ async function respondImage(
659
676
  }
660
677
  if (item.watermark) {
661
678
  const canvas = renderWatermark(width, height, scale, item.watermark);
662
-
663
679
  composites.push({ input: canvas.toBuffer() });
664
680
  }
665
681
 
@@ -670,7 +686,6 @@ async function respondImage(
670
686
  scale,
671
687
  item.staticAttributionText,
672
688
  );
673
-
674
689
  composites.push({ input: canvas.toBuffer() });
675
690
  }
676
691
 
@@ -687,7 +702,6 @@ async function respondImage(
687
702
  }
688
703
  // eslint-disable-next-line security/detect-object-injection -- format is validated above
689
704
  const formatQuality = formatQualities[format];
690
-
691
705
  // eslint-disable-next-line security/detect-object-injection -- format is validated above
692
706
  const formatOptions = (options.formatOptions || {})[format] || {};
693
707
 
@@ -710,25 +724,32 @@ async function respondImage(
710
724
  } else if (format === 'webp') {
711
725
  image.webp({ quality: formatOptions.quality || formatQuality || 90 });
712
726
  }
727
+
713
728
  image.toBuffer((err, buffer, info) => {
714
- if (!buffer) {
715
- return res.status(404).send('Not found');
729
+ if (err || !buffer) {
730
+ console.error('Sharp error:', err);
731
+ if (!res.headersSent) {
732
+ return res.status(500).send('Image processing failed');
733
+ }
734
+ return;
716
735
  }
717
736
 
718
- res.set({
719
- 'Last-Modified': item.lastModified,
720
- 'Content-Type': `image/${format}`,
721
- });
722
- return res.status(200).send(buffer);
737
+ if (!res.headersSent) {
738
+ res.set({
739
+ 'Last-Modified': item.lastModified,
740
+ 'Content-Type': `image/${format}`,
741
+ });
742
+ return res.status(200).send(buffer);
743
+ }
723
744
  });
724
745
  });
725
746
  } catch (error) {
726
747
  clearTimeout(renderTimeout);
727
748
  console.error('Unexpected error during render:', error);
728
749
  try {
729
- pool.destroy(renderer);
750
+ pool.removeBadObject(renderer);
730
751
  } catch (e) {
731
- console.error('Error destroying renderer after error:', e);
752
+ console.error('Error removing renderer after error:', e);
732
753
  }
733
754
  if (!res.headersSent) {
734
755
  return res.status(500).send('Render failed');