Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

Help! "Maximum call stack size exceeded" error in API script

I'm updating one of my scripts, and have hit a snag that my addled brain cannot figure out. The console sounds like it's telling me I have an infinite loop somewhere, but for the life of me I cannot understand where it could be happening. Here is the full error: RangeError: Maximum call stack size exceeded at TLSSocket.Socket.write (net.js:667:44) at IO.ondata (internal/streams/legacy.js:16:26) at emitOne (events.js:96:13) at IO.emit (events.js:191:7) at Client._write (/home/node/d20-api-server/node_modules/websocket-driver/lib/websocket/driver/base.js:158:25) at Client._sendFrame (/home/node/d20-api-server/node_modules/websocket-driver/lib/websocket/driver/hybi.js:270:10) at Client.onMessageReady (/home/node/d20-api-server/node_modules/websocket-driver/lib/websocket/driver/hybi.js:225:12) at Client.<anonymous> (/home/node/d20-api-server/node_modules/websocket-driver/lib/websocket/driver/hybi.js:231:24) at pipe (/home/node/d20-api-server/node_modules/websocket-extensions/lib/pipeline/index.js:37:40) at Pipeline._loop (/home/node/d20-api-server/node_modules/websocket-extensions/lib/pipeline/index.js:44:3) The above doesn't say where in my script it is happening, but previous errors have centered around trying to use .split() on a variable I know to be a string. Sending the variable to the console confirms this. However, it will tell me string_variable.split() is not a function... or that I have exceeded the maximum call stack size. Anyone have some insight into this problem? I am more than happy to share snippets or the whole shibang if you think you can figure this out. Thanks in advance!
1571012043
The Aaron
Roll20 Production Team
API Scripter
If you can post the code, or PM me a link, I can help you find it.  You probably have an infinite recursion happening.  This can happen quite easily if your script is reading chat messages and also sending chat messages when it receives them. A simple example: on('chat:message', msg=> sendChat('', "Infinite recursion now!") );
The Aaron said: If you can post the code, or PM me a link, I can help you find it.  You probably have an infinite recursion happening.  This can happen quite easily if your script is reading chat messages and also sending chat messages when it receives them. A simple example: on('chat:message', msg=> sendChat('', "Infinite recursion now!") ); It's not, just trying to write to a handout. I'll PM you with the info. Thanks!
1571598525

Edited 1571598555
Problem solved! I figured I'd post my findings here to help anyone else having similar problems. As access to a handout's notes is asynchronous and forces you to use a callback (see&nbsp; <a href="https://wiki.roll20.net/API:Objects#Using_the_Notes.2C_GMNotes.2C_and_Bio_fields_.5BAsynchronous.5D" rel="nofollow">https://wiki.roll20.net/API:Objects#Using_the_Notes.2C_GMNotes.2C_and_Bio_fields_.5BAsynchronous.5D</a> ), you can't read from and process/sanitize the content&nbsp; and write to the notes field all within the callback function. This will likely create an infinite loop even on an empty notes field &nbsp;and create an error similar to the one above.&nbsp;Sounds like a no-brainer out of context, but it's too easy to overlook.&nbsp; The solution is to as little as possible within the callback function, then put the remainder of your notes processing, including the handout.set() call, within a timeout function with plenty of delay to allow whatever processing being done inside the callback to be completed. In other words, change this: var new_content; var handout = findObjs({name: 'Name of Handout', type: 'handout'})[0]; handout.get('notes', function (notes) { new_content = someProcessing(notes); /* many lines of new_content processing; */ handout.set({notes: new_content}); }); To this: var new_content; var handout = findObjs({name: 'Name of Handout', type: 'handout'})[0]; handout.get('notes', function (notes) { new_content = someProcessing(notes); }); setTimeout(function () { /* many lines of new_content processing; */ handout.set({notes: new_content}); }, 500); Instead of "setTimeout" you could use the underscore.js "_.delay" function. Either way, it will definitely prevent hair loss and a murder spree or two.
1571598806

Edited 1571598824
The Aaron
Roll20 Production Team
API Scripter
Ah!&nbsp; I feel like I ran into something similar to that 3-4 years ago with Handouts! Probably this would work (and work better): var new_content; var handout = findObjs({name: 'Name of Handout', type: 'handout'})[0]; handout.get('notes', function (notes) { new_content = someProcessing(notes); /* many lines of new_content processing; */ setTimeout(()=&gt;handout.set({notes: new_content}),0); }); That will defer the set to outside the callstack of the get callback.&nbsp; The problem with the separate setTimeout() is that it can occur before the get happens, as I'm sure you ran into.&nbsp; Delaying for 500ms will work in some cases, but during peak processing times, the .get() might take longer and the .set() will become incorrect.
1571599918
GiGs
Pro
Sheet Author
API Scripter
Ben L. said: Either way, it will definitely prevent hair loss and a murder spree or two. Or enable them, since the script failing wont stall the session, and the players will be able to get on with doing what they do best!
1571600630
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
The Aaron said: Ah!&nbsp; I feel like I ran into something similar to that 3-4 years ago with Handouts! Probably this would work (and work better): var new_content; var handout = findObjs({name: 'Name of Handout', type: 'handout'})[0]; handout.get('notes', function (notes) { new_content = someProcessing(notes); /* many lines of new_content processing; */ setTimeout(()=&gt;handout.set({notes: new_content}),0); }); That will defer the set to outside the callstack of the get callback.&nbsp; The problem with the separate setTimeout() is that it can occur before the get happens, as I'm sure you ran into.&nbsp; Delaying for 500ms will work in some cases, but during peak processing times, the .get() might take longer and the .set() will become incorrect. Alternatively use promises and async/await so that you don't need to worry about the asynchronicity in your code :) var handoutGetter = async function(){ let new_content, handout = findObjs({name: 'Name of Handout', type: 'handout'})[0], notes = await new Promise((resolve,reject)=&gt;{ handout.get('notes', function (n) { resolve(n); }); }); log(notes); new_content = someProcessing(notes); /* many lines of new_content processing; */ handout.set({notes: new_content}); };
1571600722
The Aaron
Roll20 Production Team
API Scripter
Totally, but that's a bigger change, and somewhat infectious. =D I sent a minimal reproduction script for this issue to the Devs, hopefully they can use it to find and squish the bug.