I think we’re operating on different definitions of “memory unsafe”. Memory unsafe means accessing uninitialised memory, freed memory, other objects’ memory and so forth.
This article seems to simply be describing memory inefficiency in promises, that in certain situations it can leak like a sieve.
If Promise in any particular environment was not memory-safe, that would probably be a high or critical security vulnerability. Mere memory leaks are not a security issue.
(I’m operating on the reasonable assumption that the JavaScript engine is simply going OOM and crashing for that reason instead of something more sinister.)
I’m biased by being heavily steeped in Rust, but I believe this is the accepted definition of “memory unsafe”. OOM crashes don’t really get the chance to be memory unsafe—the process is killed, so all its memory vanishes like the dew in the morn, only several orders of magnitude faster.
As I understood, problem is that stack overflow happens in native code, which causes access violation error and virtual machine is killed. "Proper" stack overflow should result with JavaScript exception and virtual machine should remain in working state, the same is about memory leak.
Yeah, that seems a reasonable clarification to put on it: this is more severe than a mere memory leak, because it can be mildly weaponised to a DoS attack. But I expect it’s still only in “bug that would be nice fixed” category rather than “security issue that needs to be fixed”.
(Addition: hang on, is this crashing OOM or crashing stack overflow? I’m guessing it’s OOM, in which case it would not be a bug at all.)
...which causes access violation error and virtual machine is killed
When you write "virtual machine" do you mean the local JavaScript engine? For most people that would be just a little annoyance, the usual browser crash. I actually prefer crash to freeze.
> When you write "virtual machine" do you mean the local JavaScript engine?
Yes, of course, JavaScript VM inside a browser process or one of them.
> For most people that would be just a little annoyance, the usual browser crash. I actually prefer crash to freeze.
Any browser crash is a bug and should never happen for any input. If browser is smart enough to run separate process for different pages, this is little annoyance indeed. I remember when crashed page caused entire browser to crash and often lose open tabs. That wasn't little annoyance. Also some clever guy might figure out how to exploit this crash, I'm not sure if it's possible, but I'm not the clever one :)
It's not even a particularly unexpected leak. When you create unbounded recursion, you leak memory. This is true if you don't use Promises, and remains true when you do.
This is really just another complaint about lack of Tail-Call Optimization. This issue has come up for any number of languages and libraries. Often I think that the "O" in TCO is a misnomer. If it was really an "Optimization" it wouldn't affect correctness of the program, but there are many cases where a program is correct if TCO is happening but undefined if not. It's more akin to lazy/strict evaluation: equivalent in simple cases, a major correctness issue in more complex ones.
"Forever" is obviously just a reference to would happen with memory usage. Proper optimized code could run "forever" (or a long time) without increasing in memory usage, ie. memory would scale as O(1) as opposed to O(n).
> This is really just another complaint about lack of Tail-Call Optimization
TCO needs to be supported by the runtime, but what I'm talking about only needs to be supported by the Promise implementation, so in this case it's just a sloppy library we are talking about.
> When you create unbounded recursion, you leak memory
This is only true in weird universes, like that of JavaScript.
It's actually really easy to end up with a really long chain of tail recursive calls - for example when processing streams or when doing functional programming in general.
Having to explain why this is a huge problem is one reason for why I feel that JavaScript's ecosystem is so broken. In case you're wondering, given Promise's surprises, I actually think going for the callback hell is saner, because in such cases you just pass a callback around without surprises.
TCO is classically a compiler/runtime optimization, but that's only because people didn't commonly implement fundamental control flow as libraries until relatively recently. Whether it is the runtime introspecting function calls to decide whether it can elide the current stack frame, or a futures library introspecting return values to decide whether the current future can delegate to the returned one, it's the same concept.
As for "weird universes", it is typically only functional programming languages that guarantee TCO as part of their standard. Most programmers live in a world where they can't rely on their implementation to optimize tail-calls, which is to say they don't make unbounded recursive calls. If they do, they end up with an insidious form of non-portable code.
That's it. It's like a stack overflow. The code contains a function which calls itself via a Promise. There's no guard which will eventually terminate the recursion.
> His code is creating an infinite promise chain. Did the author really expect it would not eat all his memory and crash?
Processing an infinite or really long promise chain is what ends up happening when you're working with streams and functional programming in general.
Yes, I expected it to not leak memory, because sane implementations don't leak. One such implementation is Scala's Future [0], using an implementation trick which I then ported in Funfix.org, so it's definitely possible.
> If the author had written the same code with callbacks, a stack overflow would have been thrown.
That's not true. For one, asynchronous calls will not trigger stack overflows. And a callback approach won't leak heap memory either, because you don't have to return a value-wannabe to the caller, so you're not building a stack.
> So misunderstanding of promises (and/or async coding) is the problem here, not promises.
I'm involved in no less than 3 libraries [1] [2] [3] implementing Promise and Haskell IO alternatives in JavaScript and Scala, so I'm pretty sure that I understand promises by now :-)
My bad, I was probably a bit aggressive and didn't read through. Thank you for correcting a few points.
However, I'm still convinced that calling Promises "memory unsafe" is largely incorrect:
- if it wasn't for the setImmediate, I insist the callback version would cause a stack overflow
- you can't create an infinite number of objects (promises in this case) linked to each other without expecting a memory overflow. If it works in the Future example, it means the Futures are somehow not connected to each other, which must violate the contract for Promises. Can a Future object be resolved/flatMapped more than once without rerunning, ie. does it cache its value? I suspect this is the difference between a Promise and a Future...
- "You can't have infinite loops with Promises" would make a better title
23 comments
[ 1.6 ms ] story [ 61.8 ms ] threadThis article seems to simply be describing memory inefficiency in promises, that in certain situations it can leak like a sieve.
If Promise in any particular environment was not memory-safe, that would probably be a high or critical security vulnerability. Mere memory leaks are not a security issue.
(I’m operating on the reasonable assumption that the JavaScript engine is simply going OOM and crashing for that reason instead of something more sinister.)
Indeed, it's not a security issue.
(Addition: hang on, is this crashing OOM or crashing stack overflow? I’m guessing it’s OOM, in which case it would not be a bug at all.)
When you write "virtual machine" do you mean the local JavaScript engine? For most people that would be just a little annoyance, the usual browser crash. I actually prefer crash to freeze.
Yes, of course, JavaScript VM inside a browser process or one of them.
> For most people that would be just a little annoyance, the usual browser crash. I actually prefer crash to freeze.
Any browser crash is a bug and should never happen for any input. If browser is smart enough to run separate process for different pages, this is little annoyance indeed. I remember when crashed page caused entire browser to crash and often lose open tabs. That wasn't little annoyance. Also some clever guy might figure out how to exploit this crash, I'm not sure if it's possible, but I'm not the clever one :)
This is really just another complaint about lack of Tail-Call Optimization. This issue has come up for any number of languages and libraries. Often I think that the "O" in TCO is a misnomer. If it was really an "Optimization" it wouldn't affect correctness of the program, but there are many cases where a program is correct if TCO is happening but undefined if not. It's more akin to lazy/strict evaluation: equivalent in simple cases, a major correctness issue in more complex ones.
TCO needs to be supported by the runtime, but what I'm talking about only needs to be supported by the Promise implementation, so in this case it's just a sloppy library we are talking about.
> When you create unbounded recursion, you leak memory
This is only true in weird universes, like that of JavaScript.
It's actually really easy to end up with a really long chain of tail recursive calls - for example when processing streams or when doing functional programming in general.
Having to explain why this is a huge problem is one reason for why I feel that JavaScript's ecosystem is so broken. In case you're wondering, given Promise's surprises, I actually think going for the callback hell is saner, because in such cases you just pass a callback around without surprises.
As for "weird universes", it is typically only functional programming languages that guarantee TCO as part of their standard. Most programmers live in a world where they can't rely on their implementation to optimize tail-calls, which is to say they don't make unbounded recursive calls. If they do, they end up with an insidious form of non-portable code.
This! Exactly this.
If the author had written the same code with callbacks, a stack overflow would have been thrown.
So misunderstanding of promises (and/or async coding) is the problem here, not promises.
Processing an infinite or really long promise chain is what ends up happening when you're working with streams and functional programming in general.
Yes, I expected it to not leak memory, because sane implementations don't leak. One such implementation is Scala's Future [0], using an implementation trick which I then ported in Funfix.org, so it's definitely possible.
> If the author had written the same code with callbacks, a stack overflow would have been thrown.
That's not true. For one, asynchronous calls will not trigger stack overflows. And a callback approach won't leak heap memory either, because you don't have to return a value-wannabe to the caller, so you're not building a stack.
> So misunderstanding of promises (and/or async coding) is the problem here, not promises.
I'm involved in no less than 3 libraries [1] [2] [3] implementing Promise and Haskell IO alternatives in JavaScript and Scala, so I'm pretty sure that I understand promises by now :-)
I'm also pretty fond of this article I wrote a while back, asynchrony being sort of a fetish of mine: https://alexn.org/blog/2017/01/30/asynchronous-programming-s...
[0] http://www.scala-lang.org/api/2.12.3/scala/concurrent/Future...
[1] https://monix.io
[2] https://github.com/typelevel/cats-effect
[3] https://funfix.org
However, I'm still convinced that calling Promises "memory unsafe" is largely incorrect:
- if it wasn't for the setImmediate, I insist the callback version would cause a stack overflow
- you can't create an infinite number of objects (promises in this case) linked to each other without expecting a memory overflow. If it works in the Future example, it means the Futures are somehow not connected to each other, which must violate the contract for Promises. Can a Future object be resolved/flatMapped more than once without rerunning, ie. does it cache its value? I suspect this is the difference between a Promise and a Future...
- "You can't have infinite loops with Promises" would make a better title
I'm sorry, however, for trolling.
Cheers
Given that it has more points and it is newer than some articles on the front page right now, I'm guessing I've hurt people's feelings.
As one of its long time visitors and contributors, I am disappointed by HN, but that's not news.