From 1d4c1ff1887b7bdfdeba316a85a22fd764d34055 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 31 Jul 2024 15:51:56 +0300 Subject: [PATCH] fix: avoid synchronous calls where possible (#1320) Signed-off-by: Aarni Koskela --- src/main.js | 18 ++++++++++-------- src/promises.js | 14 ++++++++++++++ src/serve_data.js | 20 ++++++++++---------- src/serve_rendered.js | 15 ++++++++------- src/serve_style.js | 2 +- src/utils.js | 5 +++-- 6 files changed, 46 insertions(+), 28 deletions(-) create mode 100644 src/promises.js diff --git a/src/main.js b/src/main.js index 5077ba0..129b21d 100644 --- a/src/main.js +++ b/src/main.js @@ -3,6 +3,7 @@ 'use strict'; import fs from 'node:fs'; +import fsp from 'node:fs/promises'; import path from 'path'; import { fileURLToPath } from 'url'; import axios from 'axios'; @@ -23,6 +24,7 @@ if (args.length >= 3 && args[2][0] !== '-') { } import { program } from 'commander'; +import { existsP } from './promises.js'; program .description('tileserver-gl startup options') .usage('tileserver-gl [mbtiles] [options]') @@ -95,7 +97,7 @@ const startWithInputFile = async (inputFile) => { inputFile = path.resolve(process.cwd(), inputFile); inputFilePath = path.dirname(inputFile); - const inputFileStats = fs.statSync(inputFile); + const inputFileStats = await fsp.stat(inputFile); if (!inputFileStats.isFile() || inputFileStats.size === 0) { console.log(`ERROR: Not a valid input file: `); process.exit(1); @@ -140,11 +142,11 @@ const startWithInputFile = async (inputFile) => { }; } - const styles = fs.readdirSync(path.resolve(styleDir, 'styles')); + const styles = await fsp.readdir(path.resolve(styleDir, 'styles')); for (const styleName of styles) { const styleFileRel = styleName + '/style.json'; const styleFile = path.resolve(styleDir, 'styles', styleFileRel); - if (fs.existsSync(styleFile)) { + if (await existsP(styleFile)) { config['styles'][styleName] = { style: styleFileRel, tilejson: { @@ -189,7 +191,7 @@ const startWithInputFile = async (inputFile) => { process.exit(1); } - instance.getInfo((err, info) => { + instance.getInfo(async (err, info) => { if (err || !info) { console.log('ERROR: Metadata missing in the MBTiles.'); console.log( @@ -207,11 +209,11 @@ const startWithInputFile = async (inputFile) => { mbtiles: path.basename(inputFile), }; - const styles = fs.readdirSync(path.resolve(styleDir, 'styles')); + const styles = await fsp.readdir(path.resolve(styleDir, 'styles')); for (const styleName of styles) { const styleFileRel = styleName + '/style.json'; const styleFile = path.resolve(styleDir, 'styles', styleFileRel); - if (fs.existsSync(styleFile)) { + if (await existsP(styleFile)) { config['styles'][styleName] = { style: styleFileRel, tilejson: { @@ -254,10 +256,10 @@ fs.stat(path.resolve(opts.config), async (err, stats) => { return startWithInputFile(inputFile); } else { // try to find in the cwd - const files = fs.readdirSync(process.cwd()); + const files = await fsp.readdir(process.cwd()); for (const filename of files) { if (filename.endsWith('.mbtiles') || filename.endsWith('.pmtiles')) { - const inputFilesStats = fs.statSync(filename); + const inputFilesStats = await fsp.stat(filename); if (inputFilesStats.isFile() && inputFilesStats.size > 0) { inputFile = filename; break; diff --git a/src/promises.js b/src/promises.js new file mode 100644 index 0000000..6e13c79 --- /dev/null +++ b/src/promises.js @@ -0,0 +1,14 @@ +import util from 'node:util'; +import fsp from 'node:fs/promises'; +import zlib from 'zlib'; + +export const gzipP = util.promisify(zlib.gzip); +export const gunzipP = util.promisify(zlib.gunzip); +export const existsP = async (path) => { + try { + await fsp.access(path); // Defaults to F_OK: indicating that the file is visible to the calling process + return true; + } catch (err) { + return false; + } +}; diff --git a/src/serve_data.js b/src/serve_data.js index 7f1284d..0e37cc6 100644 --- a/src/serve_data.js +++ b/src/serve_data.js @@ -1,8 +1,7 @@ 'use strict'; -import fs from 'node:fs'; +import fsp from 'node:fs/promises'; import path from 'path'; -import zlib from 'zlib'; import clone from 'clone'; import express from 'express'; @@ -10,12 +9,13 @@ import MBTiles from '@mapbox/mbtiles'; import Pbf from 'pbf'; import { VectorTile } from '@mapbox/vector-tile'; -import { getTileUrls, isValidHttpUrl, fixTileJSONCenter } from './utils.js'; +import { fixTileJSONCenter, getTileUrls, isValidHttpUrl } from './utils.js'; import { - openPMtiles, getPMtilesInfo, getPMtilesTile, + openPMtiles, } from './pmtiles_adapter.js'; +import { gunzipP, gzipP } from './promises.js'; export const serve_data = { init: (options, repo) => { @@ -89,12 +89,12 @@ export const serve_data = { headers['Content-Encoding'] = 'gzip'; res.set(headers); - data = zlib.gzipSync(data); + data = await gzipP(data); return res.status(200).send(data); } } else if (item.sourceType === 'mbtiles') { - item.source.getTile(z, x, y, (err, data, headers) => { + item.source.getTile(z, x, y, async (err, data, headers) => { let isGzipped; if (err) { if (/does not exist/.test(err.message)) { @@ -114,7 +114,7 @@ export const serve_data = { data.slice(0, 2).indexOf(Buffer.from([0x1f, 0x8b])) === 0; if (options.dataDecoratorFunc) { if (isGzipped) { - data = zlib.unzipSync(data); + data = await gunzipP(data); isGzipped = false; } data = options.dataDecoratorFunc(id, 'data', data, z, x, y); @@ -126,7 +126,7 @@ export const serve_data = { headers['Content-Type'] = 'application/json'; if (isGzipped) { - data = zlib.unzipSync(data); + data = await gunzipP(data); isGzipped = false; } @@ -151,7 +151,7 @@ export const serve_data = { res.set(headers); if (!isGzipped) { - data = zlib.gzipSync(data); + data = await gzipP(data); } return res.status(200).send(data); @@ -212,7 +212,7 @@ export const serve_data = { }; if (!isValidHttpUrl(inputFile)) { - const inputFileStats = fs.statSync(inputFile); + const inputFileStats = await fsp.stat(inputFile); if (!inputFileStats.isFile() || inputFileStats.size === 0) { throw Error(`Not valid input file: "${inputFile}"`); } diff --git a/src/serve_rendered.js b/src/serve_rendered.js index c6953e7..a159997 100644 --- a/src/serve_rendered.js +++ b/src/serve_rendered.js @@ -17,7 +17,6 @@ import fs from 'node:fs'; import path from 'path'; import url from 'url'; import util from 'util'; -import zlib from 'zlib'; import sharp from 'sharp'; import clone from 'clone'; import Color from 'color'; @@ -42,6 +41,8 @@ import { getPMtilesTile, } from './pmtiles_adapter.js'; import { renderOverlay, renderWatermark, renderAttribution } from './render.js'; +import fsp from 'node:fs/promises'; +import { gunzipP } from './promises.js'; const FLOAT_PATTERN = '[+-]?(?:\\d+|\\d+.?\\d+)'; const PATH_PATTERN = @@ -943,7 +944,7 @@ export const serve_rendered = { callback(null, response); } } else if (sourceType === 'mbtiles') { - source.getTile(z, x, y, (err, data, headers) => { + source.getTile(z, x, y, async (err, data, headers) => { if (err) { if (options.verbose) console.log('MBTiles error, serving empty', err); @@ -962,7 +963,7 @@ export const serve_rendered = { if (format === 'pbf') { try { - response.data = zlib.unzipSync(data); + response.data = await gunzipP(data); } catch (err) { console.log( 'Skipping incorrect header for tile mbtiles://%s/%s/%s/%s.pbf', @@ -1039,7 +1040,7 @@ export const serve_rendered = { const styleFile = params.style; const styleJSONPath = path.resolve(options.paths.styles, styleFile); try { - styleJSON = JSON.parse(fs.readFileSync(styleJSONPath)); + styleJSON = JSON.parse(await fsp.readFile(styleJSONPath)); } catch (e) { console.log('Error parsing style file'); return false; @@ -1145,7 +1146,7 @@ export const serve_rendered = { } if (!isValidHttpUrl(inputFile)) { - const inputFileStats = fs.statSync(inputFile); + const inputFileStats = await fsp.stat(inputFile); if (!inputFileStats.isFile() || inputFileStats.size === 0) { throw Error(`Not valid PMTiles file: "${inputFile}"`); } @@ -1187,9 +1188,9 @@ export const serve_rendered = { } } else { queue.push( - new Promise((resolve, reject) => { + new Promise(async (resolve, reject) => { inputFile = path.resolve(options.paths.mbtiles, inputFile); - const inputFileStats = fs.statSync(inputFile); + const inputFileStats = await fsp.stat(inputFile); if (!inputFileStats.isFile() || inputFileStats.size === 0) { throw Error(`Not valid MBTiles file: "${inputFile}"`); } diff --git a/src/serve_style.js b/src/serve_style.js index 3c0c75a..44d5e04 100644 --- a/src/serve_style.js +++ b/src/serve_style.js @@ -87,7 +87,7 @@ export const serve_style = { let styleFileData; try { - styleFileData = fs.readFileSync(styleFile); + styleFileData = fs.readFileSync(styleFile); // TODO: could be made async if this function was } catch (e) { console.log('Error reading style file'); return false; diff --git a/src/utils.js b/src/utils.js index 550a0da..14e5871 100644 --- a/src/utils.js +++ b/src/utils.js @@ -2,9 +2,10 @@ import path from 'path'; import fsPromises from 'fs/promises'; -import fs, { existsSync } from 'node:fs'; +import fs from 'node:fs'; import clone from 'clone'; import { combine } from '@jsse/pbfont'; +import { existsP } from './promises.js'; /** * Restrict user input to an allowed set of options. @@ -225,7 +226,7 @@ export const listFonts = async (fontPath) => { const stats = await fsPromises.stat(path.join(fontPath, file)); if ( stats.isDirectory() && - existsSync(path.join(fontPath, file, '0-255.pbf')) + (await existsP(path.join(fontPath, file, '0-255.pbf'))) ) { existingFonts[path.basename(file)] = true; }