iconv-lite icon indicating copy to clipboard operation
iconv-lite copied to clipboard

Stumped on error with requiring ../encodings

Open leanderlee opened this issue 8 years ago • 37 comments

I've been stumped by this error a few times, and it's really hard to reproduce because it only happens once on a new install. So in a desperate attempt to glean more information, I'm posting this here to see if someone with a bit more knowledge can help point me in the right direction.

This error seems to be something with iconv-lite failing to require ../encodings but it does exist. What's even more strange is that if you restart the server it works fine thereafter.

Here is the stacktrace of the error:

15:40:48 err! [console] Error: Cannot find module '../encodings'
    at Function.Module._resolveFilename (module.js:326:15)
    at Function.Module._load (module.js:277:25)
    at Module.require (module.js:354:17)
    at require (internal/module.js:12:17)
    at Object.getCodec (/somepath/node_modules/body-parser/node_modules/iconv-lite/lib/index.js:61:27)
    at Object.getDecoder (/somepath/node_modules/body-parser/node_modules/iconv-lite/lib/index.js:118:23)
    at getDecoder (/somepath/node_modules/body-parser/node_modules/raw-body/index.js:44:18)
    at readStream (/somepath/node_modules/body-parser/node_modules/raw-body/index.js:218:15)
    at getRawBody (/somepath/node_modules/body-parser/node_modules/raw-body/index.js:106:12)
    at read (/somepath/node_modules/body-parser/lib/read.js:76:3)
    at jsonParser (/somepath/node_modules/body-parser/lib/types/json.js:121:5)
    at Layer.handle [as handle_request] (/somepath/node_modules/express/lib/router/layer.js:95:5)
    at trim_prefix (/somepath/node_modules/express/lib/router/index.js:312:13)
    at /somepath/node_modules/express/lib/router/index.js:280:7
    at Function.process_params (/somepath/node_modules/express/lib/router/index.js:330:12)
    at next (/somepath/node_modules/express/lib/router/index.js:271:10)
    at expressInit (/somepath/node_modules/express/lib/middleware/init.js:33:5)
    at Layer.handle [as handle_request] (/somepath/node_modules/express/lib/router/layer.js:95:5)
    at trim_prefix (/somepath/node_modules/express/lib/router/index.js:312:13)
    at /somepath/node_modules/express/lib/router/index.js:280:7
    at Function.process_params (/somepath/node_modules/express/lib/router/index.js:330:12)
    at next (/somepath/node_modules/express/lib/router/index.js:271:10)
    at query (/somepath/node_modules/express/lib/middleware/query.js:49:5)
    at Layer.handle [as handle_request] (/somepath/node_modules/express/lib/router/layer.js:95:5)
    at trim_prefix (/somepath/node_modules/express/lib/router/index.js:312:13)
    at /somepath/node_modules/express/lib/router/index.js:280:7
    at Function.process_params (/somepath/node_modules/express/lib/router/index.js:330:12)
    at next (/somepath/node_modules/express/lib/router/index.js:271:10)
    at Function.handle (/somepath/node_modules/express/lib/router/index.js:176:3)
    at EventEmitter.handle (/somepath/node_modules/express/lib/application.js:173:10)
    at Server.app (/somepath/node_modules/express/lib/express.js:38:9)
    at Server.<anonymous> (/somepath/node_modules/socket.io/node_modules/engine.io/lib/server.js:434:22)
    at Server.<anonymous> (/somepath/node_modules/socket.io/lib/index.js:260:16)
    at emitTwo (events.js:87:13)
    at Server.emit (events.js:172:7)
    at HTTPParser.parserOnIncoming [as onIncoming] (_http_server.js:528:12)
    at HTTPParser.parserOnHeadersComplete (_http_common.js:88:23)
  EventEmitter.logerror [console] (application.js:629)
    at Console.error (/somepath/baymax.bundle.js:160:3265)
    at EventEmitter.logerror (/somepath/node_modules/express/lib/application.js:629:43)
    at Immediate.immediate._onImmediate (timers.js:445:18)
    at processImmediate [as _immediateCallback] (timers.js:383:17)

leanderlee avatar Apr 15 '16 18:04 leanderlee

I just encountered this in an expressjs application after upgrading from node 4.2 to 6.6.0. After upgrading node, I removed my node_modules folder and ran a fresh 'npm install'. First startup seemed healthy until posting a form to the server where I saw this issue. As mentioned by @leanderlee restarting the service seemed to fix it.

ZapRowsdower910 avatar Sep 22 '16 18:09 ZapRowsdower910

Still cannot reproduce it :( Tried the upgrade method you described and it worked without any errors..

Let me know if you're able to consistently reproduce it.

ashtuchkin avatar Sep 24 '16 21:09 ashtuchkin

Issue is due to changing the current working directory of node.js process after process starts and then you attempt to perform method using iconv-lite. The lazy require breaks since it's no longer in the same relative path. Same if node_modules somehow disappears after process start.

I think I found the solution.

Try changing:

iconv.encodings = require("../encodings");

to

iconv.encodings = require("iconv-lite/encodings");

https://github.com/ashtuchkin/iconv-lite/blob/master/lib/index.js#L61

Marak avatar Dec 18 '16 09:12 Marak

I can reliably recreate this error in our test environment, we deploy node and mode_module updates packaged into an rpm, and in our latest version, our code is crashing on body parsing due to its use of this library. restarting our node service fixes this issue, it only happens right after we deploy the rpm.

dbussert avatar Jan 30 '17 20:01 dbussert

@dbussert Did you see my comment?

Marak avatar Jan 30 '17 20:01 Marak

Guys I need your help here. I don't have an environment to reproduce it, so I'm counting on you to isolate the exact requirements for this to happen.

I'm also not fond of changing that line to iconv.encodings = require("iconv-lite/encodings"); because that is a workaround for require() bug - it should be fixed on their side. By specification, require should work relative to the caller .js file and it's a much larger issue if it doesn't.

ashtuchkin avatar Feb 09 '17 04:02 ashtuchkin

@ashtuchkin -

This isn't something which requires you setup a reproducible environment to isolate or fix the bug.

It seems to be simple. Looking at https://github.com/ashtuchkin/iconv-lite/blob/master/lib/index.js#L61 , we can see that we have a "lazy require" statement.

In other words, we have a require statement which isn't being executed until getCodec function is executed. This is not a bug in require(). There are many reasons and conditions which could cause a lazy require statement to fail.

In the case I hit ( a production service serving millions of requests ), the Node.js process was being chroot to a new directory after the process started. This means that the relative path of ../encodings is no longer valid, hence the errors. Generally, it's not safe to do a lazy require of modules.

Is there a reason that ../encodings.js file is being loaded lazily in getCodec? Could it be required outside the function scope?

Also, you could probably replicate the bug using https://www.npmjs.com/package/chroot library.

Marak avatar Feb 09 '17 05:02 Marak

Thanks for the extended explanation, @Marak.

Generally, it's not safe to do a lazy require of modules.

It looks to me that generally, it's not safe to do a chroot of a node process, because node's module caching has unexpected results for dynamic requires. Are you sure other libraries/modules don't use it? It's a perfectly valid mode of loading modules in node.js, as far as I know.

Is there a reason that ../encodings.js file is being loaded lazily in getCodec?

Yes, it's an explicit optimization of startup time. Encodings tree is large and can take several hundred ms to load. I don't want to impose this tax on all projects indirectly requiring iconv-lite because they might not use (or even know about) it at all.

I still don't know why it works the second time for @dbussert - module caching should not depend on that.

ashtuchkin avatar Feb 09 '17 05:02 ashtuchkin

@ashtuchkin -

Lazy loading of Node.js modules is not a best practice and can cause several issues. You should understand that require is a synchronous and blocking statement. The same 100ms you are saving on initial load time will now be 100ms of blocked process time when getCodec is first called. I could go on and on. I won't. You are wrong, sorry.

Regardless, I understand the need for optimization on start time. It's a trade-off you have to make. Based on what I see here, the majority of users will never experience this require failing issue and will benefit from the optimized start time. HOWEVER, some users are experiencing this issue and I'm telling you exactly why it's happening.

You should either figure out a fix, or tell us you will not fix it. I already proposed the solution we used in production. What none of us need here is reasons why you don't think this is broken.

Cheers.

Marak avatar Feb 09 '17 06:02 Marak

Dear @Marak,

I'm yet to find evidence that others are experiencing the same issue as you are. All the other people in this thread have a situation where a restart "fixes" the problem, which should not happen with a chroot. Also I haven't seen any mention of chroot other than from you. So, I'm still gathering information about that case and would prefer you stop interfering and going personal about it. There's no need to preach about best practices, tell me what I should do, or talk like you represent all the other people in this thread.

As for your particular problem - no, I won't fix it for you. The core reason you have this problem is you built a system based on a subtly incorrect assumption that the node module system works correctly after a chroot. This subtle incorrectness showed itself when you started using iconv-lite, but it has potential to show for other modules as well. You cannot assume that other modules won't use a valid system interface because you think it's "not a best practice". I'm glad you found a workaround for your particular system, but 1) it's not guaranteed to work for other people, and 2) you'll need to find similar workarounds for other libraries/modules. I would've recommended to stop relying on the assumption above and try to fix your system in a more general way, like using a separate process to do the chroot and only start your original node process after that, but I don't have enough context about your system.

To be clear, I'm not being stubborn about making the change you suggested. I'm not doing that because:

  1. As I said earlier, I don't have evidence that this what's actually happening for the OP and other people in this thread.
  2. Each part of the current solution is there for a good reason:
    1. Dynamic require provides the dont-pay-if-you-dont-use guarantee for users with regards to startup time, as we discussed previously.
    2. Relative require path guarantees that I'm requiring file from the same exact module. As you might already know, there can be several versions of a module installed in a node_modules folder and I have no idea which one of them will be resolved to a top-level 'iconv-lite'. I hope I don't have to explain why this requirement is important.

ashtuchkin avatar Feb 09 '17 21:02 ashtuchkin

Have you considered what is going to happen if someone is running a webserver with 1000s of requests per second and then getCodec method is called for the first time? I believe all requests will block until encodings file completes load.

I hit this problem ( of not being able to find encodings file ), found the solution, and posted it here. You want to lazy require a huge file with a relative path, go for it. Every-time anyone hits any environment issue with express where node_modules is not where it's suppose to be after process start is going to google and find this Github Issue. It's why you are seeing a variety of issues reported here with seemingly different sources and behaviors...

Marak avatar Feb 09 '17 21:02 Marak

The lazy loading also seems to be causing an issue in other libraries... https://github.com/facebook/jest/issues/2605

jcreamer898 avatar Mar 28 '17 16:03 jcreamer898

I got the same problem as @Marak is describing, and it does not solve if I restart. His fix worked for me. And I agree with @Marak. Instead of excusing this behaviour, treat it as the bug it is and fix, please.

kleggas avatar Jul 26 '17 22:07 kleggas

I'm experiencing the same issue when executing a script within my sandboxed test environment (Jest) which uses iconv-lite as a dependency.

When I'm not lazy-loading encodings/index.js (but instead requiring everything directory at the top of the module) everything works fine for me.

The initial reason for lazy-loading this module was because of speed issues, which were fixed in PR https://github.com/ashtuchkin/iconv-lite/pull/26.

I ran this script twice (both using lazy-loading and without) and got the following results:

With lazy-loading:

❯ node -e "require('./lib/index')"  0.18s user 0.02s system 98% cpu 0.204 total

Without lazy-loading (changed iconv.encodings = require('../encodings') at the top of the file):

❯ node -e "require('./lib/index')"  0.18s user 0.02s system 98% cpu 0.204 total

The results seem to be exactly the same. I'm not sure if something changed within Node itself, or if I'm doing something wrong, but if this those stats are correct there would be no reason to lazy-load the encodings anyway.

vernondegoede avatar Nov 19 '17 14:11 vernondegoede

Still experiencing this problem. I am running inside a Docker environment.

FrenchMajesty avatar Nov 15 '18 04:11 FrenchMajesty

Running npm install again and then restarting the app fixed the problem for me.

amirmog avatar Nov 21 '18 19:11 amirmog

2019/12 same problem here

selient avatar Dec 02 '19 10:12 selient

Facing this issue with Electron and the asar package format, used the patch-package package to disable lazy-loading, and everything is now working.

octalmage avatar Mar 13 '20 19:03 octalmage

@octalmage there are several different problems discussed in this issue, could you provide more info about your setup to help me understand what's happening? Thanks!

ashtuchkin avatar Jun 28 '20 09:06 ashtuchkin

@ashtuchkin -

I've been reviewing this thread again since it popped up in my feed.

Re-reading my old posts I can see the language and tone I was using wasn't the best for collaboration.

If you'd like to get this issue closed I believe all the information needed is here posted above with my comments and other user's comments.

I would consider removing the lazy loading entirely or make it a configurable option which is disabled by default.

Marak avatar Jul 15 '20 14:07 Marak

This issue happens whenever I try to POST JSON data to the server. It's in chroot jail, but removing that does not fix it. Nor does reinstalling node_modules or restarting the server. Express goes to decode the payload and fails to lazy load the encoding module. This issue has been open for four years - time to actually fix it?

pierznj avatar Oct 15 '20 22:10 pierznj

It's worth mentioning that I use node 12.18.0, express 4.16.2 and body-parser 1.18.3 all in Docker, everything was fine until the 18th December 2020 when upgrading Docker Desktop from 3.01 to 3.0.2 I started seeing this error:

api_1  | Error: Cannot find module '../encodings'
api_1  | Require stack:
api_1  | - /usr/src/app/node_modules/iconv-lite/lib/index.js
api_1  | - /usr/src/app/node_modules/raw-body/index.js
api_1  | - /usr/src/app/node_modules/body-parser/lib/read.js
api_1  | - /usr/src/app/node_modules/body-parser/lib/types/json.js
api_1  | - /usr/src/app/node_modules/body-parser/index.js
api_1  | - /usr/src/app/node_modules/express/lib/express.js
api_1  | - /usr/src/app/node_modules/express/index.js
api_1  | - /usr/src/app/server.js
...

Rolling back to 3.0.1 fixed the issue but means I'm unable to update Docker for now.

paulgain avatar Dec 21 '20 16:12 paulgain

Thanks for your report! Could you add a bit more details into your setup?

Are you running your code from a mounted folder? Do you unmount it yourself after it starts? Could you check if it's being unmounted by docker itself?

The exception only occurs if node process doesn't have access to node_modules some time after starting, so I'm trying to understand what exactly happens.

I don't see anything relevant in docker release notes https://docs.docker.com/docker-for-mac/release-notes/#docker-desktop-community-302

Alex

On Mon, Dec 21, 2020, 11:32 Paul Gain [email protected] wrote:

It's worth mentioning that I use node 12.18.0, express 4.16.2 and body-parser 1.18.3 all in docker, everything was fine until the 18th December 2020 when upgrading docker desktop from 3.01 to 3.0.2 I started seeing this error:

api_1 | Error: Cannot find module '../encodings' api_1 | Require stack: api_1 | - /usr/src/app/node_modules/iconv-lite/lib/index.js api_1 | - /usr/src/app/node_modules/raw-body/index.js api_1 | - /usr/src/app/node_modules/body-parser/lib/read.js api_1 | - /usr/src/app/node_modules/body-parser/lib/types/json.js api_1 | - /usr/src/app/node_modules/body-parser/index.js api_1 | - /usr/src/app/node_modules/express/lib/express.js api_1 | - /usr/src/app/node_modules/express/index.js api_1 | - /usr/src/app/server.js ...

Rolling back to 3.0.1 fixed the issue but means I'm unable to update docker for now.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/ashtuchkin/iconv-lite/issues/118#issuecomment-749063597, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAEZKHNCWZ6L76S2TQ2SLNLSV52A7ANCNFSM4CBCI3HA .

ashtuchkin avatar Dec 21 '20 17:12 ashtuchkin

@ashtuchkin I encountered the exact same problem you mentioned in your initial error. I am running a Node server on a Xenial VM on GCP and after running my app.js and making a POST request to send an email, it makes use of iconv-lite at some point and throws cannot find module '..encodings/'. Like one of the suggestions I ran npm install on the VM and it worked. But that defeats the purpose of a pipeline which is actually how I'm deploying my application. So is there any fix for this yet? Or do i have to add another npm install after deploying it.. which is a very inconvenient solution.

MeanManMachineMan avatar Mar 15 '21 14:03 MeanManMachineMan

@MeanManMachineMan can you provide more details about how you're deploying your app.js? Do you bundle it somehow? I'm trying to understand whether the encodings/ directory disappears after you're starting the app or it just never getting shipped to the VM.

As a stop-gap, you can always manually require the module from your exception in your app, so that it's in the cache instead of loading on-demand.

ashtuchkin avatar Mar 15 '21 16:03 ashtuchkin

@ashtuchkin Yes certainly. I don't bundle my app.js since it's not a very big file and it just needs to act as a server for a few features. I simply serve it with pm2 start app.js. you can always manually require the module from your exception in your app Could you please elaborate on this?

MeanManMachineMan avatar Mar 15 '21 18:03 MeanManMachineMan

You mentioned it works if you run npm install on the VM, so you usually don't do it, right? How do you get node_modules directory on the VM in the first place?

Could you please elaborate on this?

// If you import iconv-lite like this:
const iconv = require('iconv-lite');

// Then add this line right below to preload encodings:
iconv.encode("", "utf8");  // or whatever encoding you use.

// alternatively, you can use this:
require("iconv-lite/encodings");

ashtuchkin avatar Mar 15 '21 18:03 ashtuchkin

Ah I can see why the confusion has risen haha. I meant to say @dbussert had said that reinstalling it works. So after I have deployed the application through my pipeline (which runs npm install once and gets the node server up and running), I ssh into the vm, navigate to the app folder and run npm install once again and that seems to fix it. Oh but I don't use iconv-lite directly. It's getting called by body-parser :/

MeanManMachineMan avatar Mar 15 '21 18:03 MeanManMachineMan

yep that's what I'm trying to get to - how is your pipeline transferring the contents of node_modules to the vm so that it apparently loses some of the files (which you add back by doing npm install)? Is this some GCP-provided thing you're using or is it your own script? Also could you maybe attach result of ls -alR node_modules command before and after you do 'npm install' on your VM, so that I can see what changes to make it work?

As for body-parser - I believe you can access its version of iconv-lite using iconv = require("body-parser/node_modules/iconv-lite") and then use the code above.

ashtuchkin avatar Mar 15 '21 21:03 ashtuchkin

My pipeline isn't "transferring" any contents because in my repository I don't have any node_modules folder, only the package.json file. I'm using a gitlab runner for my CI pipeline, if that's what you're trying to find out. I run npm install for the first time on the VM which creates the node_modules folder inside my folder containing app.js, but then in pm2's logs I see that while POSTing some JSON data to an API running on my app.js server, it throws the module not found error. So I just run npm install again and it works like a charm. Hmm, upon navigating to my node_modules/body_parser directory in my app.js folder, there doesn't seem to be any node_modules inside body-parser, wouldn't that make my require(...) fail? Btw here is the dump of the error that my app.js returns upon making the API call after running npm install for the first time:

Error: Cannot find module '../encodings'
Require stack:
- /home/gitlab-runner/builds/BxePFZSb/0/some_name/some_name/api/node_modules/iconv-lite/lib/index.js
- /home/gitlab-runner/builds/BxePFZSb/0/some_name/some_name/api/node_modules/raw-body/index.js
- /home/gitlab-runner/builds/BxePFZSb/0/some_name/some_name/api/node_modules/body-parser/lib/read.js
- /home/gitlab-runner/builds/BxePFZSb/0/some_name/some_name/api/node_modules/body-parser/lib/types/json.js
- /home/gitlab-runner/builds/BxePFZSb/0/some_name/some_name/api/node_modules/body-parser/index.js
- /home/gitlab-runner/builds/BxePFZSb/0/some_name/some_name/api/node_modules/express/lib/express.js
- /home/gitlab-runner/builds/BxePFZSb/0/some_name/some_name/api/node_modules/express/index.js
- /home/gitlab-runner/builds/BxePFZSb/0/some_name/some_name/api/app.js
    at Function.Module._resolveFilename (node:internal/modules/cjs/loader:924:15)
    at Module.Hook._require.Module.require (/usr/lib/node_modules/pm2/node_modules/require-in-the-middle/index.js:61:29)
    at require (node:internal/modules/cjs/helpers:92:18)
    at Object.getCodec (/home/gitlab-runner/builds/BxePFZSb/0/some_name/some_name/api/node_modules/iconv-lite/lib/index.js:65:27)
    at Object.getDecoder (/home/gitlab-runner/builds/BxePFZSb/0/some_name/some_name/api/node_modules/iconv-lite/lib/index.js:127:23)
    at getDecoder (/home/gitlab-runner/builds/BxePFZSb/0/some_name/some_name/api/node_modules/raw-body/index.js:45:18)
    at readStream (/home/gitlab-runner/builds/BxePFZSb/0/some_name/some_name/api/node_modules/raw-body/index.js:180:15)
    at getRawBody (/home/gitlab-runner/builds/BxePFZSb/0/some_name/some_name/api/node_modules/raw-body/index.js:108:12)
    at read (/home/gitlab-runner/builds/BxePFZSb/0/some_name/some_name/api/node_modules/body-parser/lib/read.js:77:3)
    at jsonParser (/home/gitlab-runner/builds/BxePFZSb/0/some_name/some_name/api/node_modules/body-parser/lib/types/json.js:135:5)

MeanManMachineMan avatar Mar 16 '21 04:03 MeanManMachineMan

@ashtuchkin I actually figured out why I was getting that error dump. When I logged into the VM in my api directory I saw no node_modules folder. But since I was using express in my app and node wasn't complaining about express not being found, I assumed it was finding express somewhere and I didn't worry about it. But now I remembered that on sunday I ssh'd into the VM and ran npm install -g express which installed it in /usr/lib/node_modules.... So for running app.js and serving a default / route, it didn't throw any errors. But since iconv-lite uses lazy-loading to call encodings, I saw the error only when I was using the function stub that called body-parser (please correct me if my understanding of this is wrong), i.e my POST route. Now this error was actually thrown because I didn't have a node-modules folder in my app.js director because of the gitlab_runner jobs. So what happens when you define multiple jobs in your yaml file is that the work of one job is deleted by the subsequent job and so on, and i was absolutely in the dark about this fact and so I couldn't figure out for the life of me why node couldn't even find express initially(which is why i went ahead and did a global install of express just for the heck of it without really understanding why it wasn't working). So now after I put all my npm install statements of my react and node application into the same job, it goes and works like a charm. So to summarize, if you have multiple npm install statements required for your application, keep them in the same job or else the node_modules of the previous job will be overwritten and it will throw all sorts of module not found errors.

MeanManMachineMan avatar Mar 16 '21 06:03 MeanManMachineMan

Thanks for digging through it @MeanManMachineMan! It's becoming much clearer now. Basically node_modules directory was being cleaned up by a different task while the test was running, right? That would definitely result in this error. Let me know if you need anything else.

ashtuchkin avatar Mar 16 '21 16:03 ashtuchkin

@ashtuchkin Yes, you are correct! I do have another question through, why didn't app.js complain about body-parser not being found(since node_modules didn't exist) before jumping straight to ../encodings? Is it because I had explicitly require(..)'d body-parser in my app so it was somehow getting loaded into some sort of local context(I don't know if that's the right word for it)? I'm a bit hazy about that part and I'd be glad if you could shed some light on that for me.

MeanManMachineMan avatar Mar 16 '21 18:03 MeanManMachineMan

When you run app.js, first thing it does is it loads all the required modules in memory. For most apps it means that you can delete node_modules directory after your process has started and it will work just fine.

Iconv-lite is an outlier in this regard as it can take from 20 to 150ms to load all the character tables (especially chinese, japanese encodings). This translates directly to startup time of any app that uses e.g. express framework. As encoding conversion is not actually needed for 99.99% apps, it makes sense to lazy-load character tables so that you pay this latency penalty only if you actually need them. If at that point node_modules folder is not present, then that error happens.

ashtuchkin avatar Mar 16 '21 19:03 ashtuchkin

Aha that clears it up pretty much. Thanks for taking out your time to help me @ashtuchkin!

MeanManMachineMan avatar Mar 20 '21 11:03 MeanManMachineMan