Option in "Link-Call" to discard response after timeout

The addition of "Call-link" with "Return to Sender" has been a wonderful improvement in Node-red, and we use it everywhere.

Call-link includes a timeout parameter (which throws an exception). Yet once complete, it always returns a response, even after timeout.

Sometimes we may wish to discard the call-link response after timeout. For example, upon timeout of a DB query we may want to retry, and do not want to get old responses coming out of nowhere. Today we work around this using a design pattern which keeps track of active "Call-link" msg ids in a dynamic map. Upon catching a timeout, we remove the msg id, and thus can ignore the actual response which arrives later.

It would be nice to have this built in the Call-link node, allowing graceful timeout handling:

  • Receive the response msg with a timeout indicator (not having to set up an additional catch node)
  • Have an option to discard the response if it arrives after timeout

@knolleary , @Steve-Mcl , could this be considered in a future release?

Thanks

+1 for the option of discarding the response after a timeout, if it is feasible. It can be a pain if an error is thrown and later the response is received, for exactly the reasons indicated by @omrid.

I'm all in with the idea, if it can be implemented.

I tried something similar in response to a related discussion. Unfortunately, the _msgid may not be stable, so the solution is not really universal.

[{"id":"729cecbb1ff5eba1","type":"link in","z":"a3965ef0f8fb91de","name":"link in 8","links":[],"x":35,"y":180,"wires":[["b146f8ae3bdafd2e","8db7717a79eeb402"]]},{"id":"b146f8ae3bdafd2e","type":"debug","z":"a3965ef0f8fb91de","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":140,"y":160,"wires":[]},{"id":"8db7717a79eeb402","type":"function","z":"a3965ef0f8fb91de","name":"save _linkSource","func":"let sources = flow.get('sources') || {}\nsources[msg._msgid] = msg._linkSource\nflow.set('sources',sources)\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":180,"y":200,"wires":[["009387c16092339e"]]},{"id":"8ec8802515434fdb","type":"link out","z":"a3965ef0f8fb91de","name":"link out 6","mode":"return","links":[],"x":805,"y":200,"wires":[]},{"id":"234fe97354c12fa6","type":"function","z":"a3965ef0f8fb91de","name":"restore _linkSource","func":"sources = flow.get('sources')\nmsg._linkSource = sources[msg._msgid]\ndelete sources[msg._msgid]\nflow.set('sources',sources)\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":630,"y":200,"wires":[["8ec8802515434fdb","2ef2916145d66652"]]},{"id":"b00e94908e30571b","type":"inject","z":"a3965ef0f8fb91de","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":240,"y":120,"wires":[["2b05c2cafc2ad77e"]]},{"id":"2b05c2cafc2ad77e","type":"link call","z":"a3965ef0f8fb91de","name":"subroutine","links":["729cecbb1ff5eba1"],"linkType":"static","timeout":"30","x":390,"y":120,"wires":[["ecf2fe114687dfad"]]},{"id":"009387c16092339e","type":"change","z":"a3965ef0f8fb91de","name":"","rules":[{"t":"delete","p":"_linkSource","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":400,"y":200,"wires":[["234fe97354c12fa6","83d7d07c358f046a"]]},{"id":"96ba466ab14b71b9","type":"link in","z":"a3965ef0f8fb91de","name":"link in 9","links":[],"x":65,"y":380,"wires":[["ebeaea69f592f596","5b53898dfdc45f4b"]]},{"id":"ebeaea69f592f596","type":"function","z":"a3965ef0f8fb91de","name":"save _linkSource","func":"let sources = flow.get('sources') || {}\nsources[msg._msgid] = msg._linkSource\nflow.set('sources',sources)\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":210,"y":400,"wires":[["b9ed36c0820eaac4"]]},{"id":"b9ed36c0820eaac4","type":"change","z":"a3965ef0f8fb91de","name":"","rules":[{"t":"delete","p":"_linkSource","pt":"msg"},{"t":"delete","p":"_msgid","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":400,"y":400,"wires":[["cf741eb2cc65f4a5","d1d8570eb5e27597"]]},{"id":"cf741eb2cc65f4a5","type":"function","z":"a3965ef0f8fb91de","name":"restore _linkSource","func":"sources = flow.get('sources')\nmsg._linkSource = sources[msg._msgid]\ndelete sources[msg._msgid]\nflow.set('sources',sources)\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":610,"y":400,"wires":[["e21c098c19d2c356","0ae61eddb0888810"]]},{"id":"e21c098c19d2c356","type":"link out","z":"a3965ef0f8fb91de","name":"link out 7","mode":"return","links":[],"x":785,"y":400,"wires":[]},{"id":"966a40766453fbeb","type":"inject","z":"a3965ef0f8fb91de","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":240,"y":320,"wires":[["ace486d9ccbad7ca"]]},{"id":"ace486d9ccbad7ca","type":"link call","z":"a3965ef0f8fb91de","name":"subroutine","links":["96ba466ab14b71b9"],"linkType":"static","timeout":"30","x":390,"y":320,"wires":[["a345d76c98f723df"]]},{"id":"42740aa9100a7075","type":"comment","z":"a3965ef0f8fb91de","name":"this works","info":"","x":380,"y":80,"wires":[]},{"id":"7e9601e9b6ee89fd","type":"comment","z":"a3965ef0f8fb91de","name":"this doesn't","info":"","x":390,"y":280,"wires":[]},{"id":"83d7d07c358f046a","type":"debug","z":"a3965ef0f8fb91de","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":590,"y":160,"wires":[]},{"id":"2ef2916145d66652","type":"debug","z":"a3965ef0f8fb91de","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":770,"y":240,"wires":[]},{"id":"ecf2fe114687dfad","type":"debug","z":"a3965ef0f8fb91de","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":530,"y":120,"wires":[]},{"id":"5b53898dfdc45f4b","type":"debug","z":"a3965ef0f8fb91de","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":170,"y":360,"wires":[]},{"id":"d1d8570eb5e27597","type":"debug","z":"a3965ef0f8fb91de","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":570,"y":360,"wires":[]},{"id":"0ae61eddb0888810","type":"debug","z":"a3965ef0f8fb91de","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":750,"y":440,"wires":[]},{"id":"a345d76c98f723df","type":"debug","z":"a3965ef0f8fb91de","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":530,"y":320,"wires":[]},{"id":"702c440c37cac94d","type":"catch","z":"a3965ef0f8fb91de","name":"","scope":null,"uncaught":false,"x":380,"y":480,"wires":[["3005e764b243a4c5"]]},{"id":"3005e764b243a4c5","type":"debug","z":"a3965ef0f8fb91de","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":510,"y":480,"wires":[]}]

Have you found a way around this? My example is not completely artificial, since for example, the core split node assigns all new id's to messages in the sequence it generates.

Does the node know how stale a returned message is?
There could be an option "Send timed out messages to separate output" cf Trigger node.

Hi all, it would be good if you could get involved in this discussion.

I had proposed an option for error handling (where you could specify if it throws or packs the error into the msg or spits it out of a 2nd output) but it looks like it may not make the cut. The may still be a way to achieve the same result with a bit group thinking?

If there are suitable ideas and clear means of implementing things I will definitely consider them.

Thanks for that link Steve.

Off-topic:
While I'm pretty sure I have no code to offer due to not having fluent Javascript, it would be valuable for my education if discussions like that contained the URL of the specific source code in question.
It's often pretty intimidating browsing through GitHub.

1 Like

Thanks Steve, let's continue the discussion there

Thanks, I wasn't aware that _msgid can change.
You can always take the crude approach: add a timestamp to the msg before sending and compare it to the current time after it returns. if elapsed > timeout then discard the response...

Timestamp, yes; before sending, no.

A node in the subroutine that replaces the entire message will remove the timestamp. The only safe place to add a timestamp is at or just before the link out (return) node. The link out node could be modified to do this. Otherwise, it would have to be done by the user in each subroutine.

I agree that the subroutine can remove the timestamp. But we need the "send" time in order to calculate the elapsed time, and Link-out is called after the subroutine execution.
Any idea how to calculate the elapsed time of the returning msg?

If you do something like this on the way in
msg.callTime = new Date().getTime()
That will add a millisecond timestamp to the message. Then when it comes back you can do
const msDiff = new Date().getTime() - msg.callTime
to give you the number of msec since it went in.

As @drmibell says, this will only work if you know that the nodes in the function do not lose the timestamp.