Automattic / node-canvas

Node canvas is a Cairo backed Canvas implementation for NodeJS.
10.17k stars 1.17k forks source link

registerFont is not reliable inside AWS Lambda #1828

Open Fawwaz-2009 opened 3 years ago

Fawwaz-2009 commented 3 years ago

Issue or Feature

I'm noticing that registerFont is not reliable inside AWS Lambda. What I've observed that if I call Lambda function half the time it would work and the other half it would not. I'm using the /tmp folder to temporally write the font files there so registerFont can load them. The error I'm saying is Error: Could not parse font file at Object.registerFont

Steps to Reproduce

Here is the piece of code loading the font

import { createWriteStream } from 'fs';
import { join, resolve } from 'path';
import { registerFont } from 'canvas';

export default function nodeFontLoader(fonts: Font[], configs: Configs) {
  const promises: Promise<any>[] = [];

  fonts.forEach(({ family, fontFileUrl }) => {
    const fontFileName = `${family}.ttf`;
    const fontFilePath = resolve(join(configs.baseWritePath, `/${fontFileName}`));

    const promise = fetch(fontFileUrl)
      .then((response) => {
        if (!response.ok) {
          throw new Error('failed to fetch font');
        }
        return new Promise((resolve, reject) => {
          const dest = createWriteStream(fontFilePath, {});
          response.body.pipe(dest);
          response.body.on('end', () => resolve('it worked'));
          dest.on('error', (error) => {
            console.log('FAILED TO WRITE FONTS TO FOLDER');
            reject(error);
          });
        });
      })
      .then(() => {
        console.log('REGISTERING FONT.....');
        registerFont(fontFilePath, { family });
        console.log('REGISTERED FONT.....');
      })
      .catch((error) => {
        console.log(error);
// THIS WHERE THE ERROR MESSAGE FIRE
        throw new Error('failed to fetch font ' + family);
      });
    promises.push(promise);
  });

  return Promise.all(promises);
}

Your Environment

apologies in advance if the above is not enough to go on, this is really at the edge of my expertises.

Edit: one more point I want to clarify is that I've already checked that if error happens because the preceding write operation has failed and turns out that is not the case. I checked by using fs.existsSync and listing the content of the /tmp folder and in both cases I can say the font file each time, before the call to registerFont

Fawwaz-2009 commented 3 years ago

I've deployed a fix that seems to be working 99% of the time, but as you can see it's a hack 😅

function canvasRegisterFont(fontFilePath: string, { family }: { family: string }, tryNumber = 1): void {
    try {
      registerFont(fontFilePath, { family });
    } catch (error) {
      if (tryNumber < 3) {
        console.log(error);
        console.log(`try number ${tryNumber} for registering the font has failed has failed, trying again....`);
        return canvasRegisterFont(fontFilePath, { family }, tryNumber + 1);
      }
    }
  }
charoitel commented 3 years ago

@Fawwaz-2009 Have you tried other / durable storage options as suggested in AWS blog post: https://aws.amazon.com/blogs/compute/choosing-between-aws-lambda-data-storage-options-in-web-apps/?

Fawwaz-2009 commented 3 years ago

hey @charoitel No I didn't but I doubt it would help. Few days back I wanted to build a demo locally to test another bug and I discovered that the issue of registerFont is not happening on AWS lambda but even in my local setup (MacOS). What is worse is that the wrapper solution approve ( canvasRegisterFont ) is not reliable on MacOS. While on Lambda the registerFont may fail twice, on MacOS it fail 10+ (didn't try to go higher).

unfortunately I'm not sure why is it happening or how to debug this.

jacobmolby commented 2 years ago

Also experiencing this.