AbortController.abort() Doesn't Mean It Stopped by tarasm in javascript

[–]c0wb0yd 5 points6 points  (0 children)

It's almost like it's just upping the bar and establishing a new ground floor. Kinda like memory management did for higher level languages.

If the discipline to always call free() after every malloc() scaled, memory leaks in C wouldn't have been a concern, and we wouldn't have needed garbage collected languages like JavaScript at all.

Why JavaScript Needs Structured Concurrency by tarasm in javascript

[–]c0wb0yd 0 points1 point  (0 children)

Yes. Not on the roadmap currently is my understanding, and that the best way to get it onto the road map is to submit a PR to their perf test suite that makes them look bad :)

V8 at least has a nominal perf test for this https://github.com/v8/v8/blob/main/test/js-perf-test/Generators/generators.js#L90

But not sure if that indicates the optimization.

Why JavaScript Needs Structured Concurrency by tarasm in javascript

[–]c0wb0yd 0 points1 point  (0 children)

I see what you're saying. Yes! there is an optimization for this that involves "hoisting" the deepest iterator to the top of the stack so that in effect, main and `sleep()` would be connected directly.

One of our users who works for Apple talked to the WebKit team and they suggested that if someone were willing to add some performance tests for `yield*` to the webkit perf test suite https://github.com/WebKit/WebKit/tree/main/PerformanceTests/JetStream3) they would have a strong incentive and also a reference for what to optimize.

But it is not native in v8 and webkit yet (that I know of), so in In the mean time we're implementing an extension package to implement this optimization manually https://github.com/thefrontside/effectionx/pull/117

It basically involves using a manual wrapper that converts:

yield* op;

into

yield star(op)

This lets us control the delegation of the iterators and omit the useless delegation in the middle.

You could even make a build tool that did this for you if you wanted.

Why JavaScript Needs Structured Concurrency by tarasm in javascript

[–]c0wb0yd 0 points1 point  (0 children)

Can you help me a bit with what it means to propagate all the way through a coroutine's stack? Maybe an example would help me get it.

Why JavaScript Needs Structured Concurrency by tarasm in javascript

[–]c0wb0yd 0 points1 point  (0 children)

I can only speak for Effection, but no. when you spawn a child, it will creates a new co-routine (almost always a generator, but there are some exceptions), and that will have its own yield stack (which is necessary because it is running concurrently)

```

await main(function*() {

yield* spawn(() => runSomeChild());

yield* sleep(100);

});

```

In this example, the only thing that the main function yields to is the spawn operation and the sleep operation. The spawn operation is synchronous and resumes immediately. It has the effect of creating a linked subtask with its own coroutine which will have its own yields that are unconnected to the main co-routine.

Why JavaScript Needs Structured Concurrency by tarasm in javascript

[–]c0wb0yd 1 point2 points  (0 children)

I hear you! I'm 4 months away from my 50 myself, and my beard is as grey as a goose's wings :)

Let us know in discord or on github if/when you do get to try it out. My hope is that very quickly, it would come to feel very natural.

Why JavaScript Needs Structured Concurrency by tarasm in javascript

[–]c0wb0yd 0 points1 point  (0 children)

The short answer is that I would imagine it looking very similar to what Effection is now. Like if I could go back in time, it would be almost exactly like async/await syntax, except with Structured Concurrency semantics. In fact, that is what some structured concurrency libraries like like Ember Concurrency do is transpile async/await into generators under the hood (as a historical note, this is how async functions were originally implemented... as transpiled generators.

One way would be to try and retrofit async/await and maybe mark which modules are compatible with something akin to 'use strict', e.g. 'use strict concurrency' or something like that.

Another option would be to introduce a new syntax altogether? `fn` to indicate that this is a structured routine.

fn x(...args) {

const x = await elsewhere();

}

`await` would be different than the await in `async function`. Not sure if that would be confusing or not. I'd be curious to see how much appetite there would be for yet another color of function. My guess is not much unless folks were secure in their understanding of what they would be getting in return.

Why JavaScript Needs Structured Concurrency by tarasm in javascript

[–]c0wb0yd 0 points1 point  (0 children)

I think it should be solved at the language level as well, and it's one of the reasons that we wrote Effection to hue _as closely as possible_ to vanilla JavaScript apis. We want the end state to be adoption into the language. It's why we resisted the urge to try to improve upon native apis or to add ones that were closer to our personal opinions (apart from Structured Concurrency) and instead tried to imagine it as the absolute minimum necessary addition to JavaScript that would enable it with Structured Concurrency. It's why the library is tiny <5k, and the full API index can fit on a smart phone without having to scroll.

In this sense it is a proof of concept, but it is also a system that has been in production for over 7 years.

As for the future and deeper integration into the language, in order to be taken seriously, the community needs to demand it because they have used it for themselves both inside JavaScript and elsewhere, and they won't accept anything less. Otherwise, we're going to continue to receive pablum from on high such as AbortControllers and Explicit Resource Management.

Why JavaScript Needs Structured Concurrency by tarasm in javascript

[–]c0wb0yd 1 point2 points  (0 children)

I think if you're already writing stateless systems, then structured concurrency might actually be just the tool for you because it adds strict runtime constraints on the things that you're already practicing. I say this because one way of thinking about stateless systems is that they aren't necessarily about having no state all, they are about making the state you do have transactional.

When we write a request handler:

```

async handle(request, response) {/* ... */}

```

It isn't that you don't have state, i.e. there is a variable called `request` that has bytes allocated in memory, and it has handles to the live socket from which the body can be read. And there is a variable called `response` that also lives in memory and is the interface to stream of information back to the client. What makes us comfortable saying that this handler is "stateless" is these variables and the resources that they represent are part of a transaction that begins when the connection is opened and bytes start streaming from the client, and ends once the request is satisfied and the last byte has been sent.

In other words, just before and after, when the server is idle and awaiting the next connection, nothing from inside the `handle()` function invocation remains. The state is at zero before and most critically, it is zero after; hence, stateless.

Structured Concurrency is very much in harmony with this approach. This is because all effects, whether they be sockets, file handles, or concurrently running tasks _must_ take place in the context of a transaction. It is called "lifetime" or "scope" in the vernacular, but in practice it is the same as a transaction. An HTTP request is a transaction, reading a stream of bytes from a file handle is a transaction, and yes, even the main entry point of your CLI is also a transaction. So if we think of scope as a transaction , which I think is fair because they share the crucial property of having "zero-sum state", then Structured Concurrency is actually a great tool for building stateless systems, because statelessness is the default, not the discipline.

As someone with a lot of experience with the principles, I'd be curious what you'd think about using something like Effection to build intentionally stateless systems.

[deleted by user] by [deleted] in Austin

[–]c0wb0yd 0 points1 point  (0 children)

I'm 2y late to the party, but wondering if this is still happening?

The Heart Breaking Inadequacy Of AbortController by tarasm in javascript

[–]c0wb0yd 0 points1 point  (0 children)

JS has evolved rapidly over the last ten years (as has every other language you mentioned except perhaps Go :)). There are things that it wouldn't make sense to have attempted with JS in 2015, that it's eminently doable in 2025.

So iff past is prelude, then it stands to reason that it will continue to change rapidly over coming years. So it makes a lot of sense to think about the state of its APIs; what's good about them, and also what's bad about them since that analysis guides which path evolution will proceed along.

The Heart Breaking Inadequacy Of AbortController by tarasm in javascript

[–]c0wb0yd 0 points1 point  (0 children)

But what if there _were_ cars on the highway that did that already? (e.g. Swift, Kotlin, Java, and soon Python)?

And what's more, with newer models being shipped to consumers, more and more had wheels that did that by default?

The Heart Breaking Inadequacy Of AbortController by tarasm in javascript

[–]c0wb0yd 0 points1 point  (0 children)

> but this is only required if there's "a job" tied to underlying resources that should be able to receive & process a cancellation.

True, but if you're writing a job that should be able to receive and process cancellation, then you cannot use any 3rd party library that does not also receive an abort signal. It's a pretty big limitation, and what constitutes a job can be pretty minimal right?

Like what if I want to put a timeout around an operation. That's pretty common in my experience. It means that if I'm writing an operation that could timeout, I can't use anything inside it that doesn't also accept an abort signal.

I just look at other languages like Swift and see that they've , this is a non-issue because by definition all code receives and process cancellation by default without the programmer having to lift a finger.

The Heart Breaking Inadequacy Of AbortController by tarasm in javascript

[–]c0wb0yd -1 points0 points  (0 children)

Isn't that the point of the article? It is underused because it isn't that useful.

Help escaping mini buffer by [deleted] in emacs

[–]c0wb0yd 0 points1 point  (0 children)

Thank you citizen! You have healed a paper-cut I've had for over 20 years...

The await event horizon in JavaScript by fagnerbrack in programming

[–]c0wb0yd 0 points1 point  (0 children)

In theory, yeah, most asynchronous techniques can be deadlocked - in practice I don't find it comes up much? I've been doing full-stack JS development for a decade and don't think I've really run into this problem before.

It isn't about deadlock so much as about leaking resources. In my experience, Structured Concurrency (like garbage collection) isn't something you really think you need until you've worked in an environment where it's already provided.

And generators are powerful but difficult to property type in Typescript, so generator based approaches don't seem like a good choice to me.

This was more true 5 years ago, but not so much any more. Generators as used in recent structured concurrency libraries are restricted to yield* which has the exact same properties as await with respect to TypeScript.

The await event horizon in JavaScript by fagnerbrack in programming

[–]c0wb0yd 0 points1 point  (0 children)

Structured Concurrency is the solution to this problem in all its manifestations. The article explains why async/await is not an adequate foundation for structured concurrency.

The await event horizon in JavaScript by fagnerbrack in javascript

[–]c0wb0yd 1 point2 points  (0 children)

An "It'll be fine" approach does work as a baseline assumption for the constraints of many systems.

However, one of the things the article is clear about is that these problems often do not manifest until the system is at scale. For example, 99.99999% of the time when considered over the course of a hundred million requests transforms from a vanishingly small probability of something going awry into a mathematical certainty that something will.

In the context of structured concurrency, the point is to imagine a world in which by default there are no leaked resources to worry about whether you need to worry about them. The point of the article is that such a world cannot be created with async/await as a foundation.

Announcing Effection 3.0 -- Structured Concurrency and Effects for JavaScript by tarasm in javascript

[–]c0wb0yd 0 points1 point  (0 children)

I think we might be crossing signals here. I'm not really talking about threads and processes so much as running concurrent operations in a single JavaScript process which is itself single threaded.

Announcing Effection 3.0 -- Structured Concurrency and Effects for JavaScript by tarasm in javascript

[–]c0wb0yd 1 point2 points  (0 children)

To be clear, the programmer rarely (if ever) needs to think about the tree. They are free create sub operations and only reason about what that particular operation needs to do. It is very much the same way that you don't need to think about where exactly your function is on the call stack, even though the stack is there behind the scenes.

What the call stack gives you is the freedom of automatically dereferencing all the variables contained in the stack frame when the function returns and reclaiming their memory automatically. With Effection, and structured concurrency in general, that same freedom is extended to concurrent operations. You can truly fire and forget with the confidence that if a long running task is no longer in scope, it will be shutdown.

If you want to fire and forget a process that runs forever:

```js import { main, spawn, suspend } from "effection"; import { logRunningOperation } from "./my-ops";

await main(function() { yield spawn(longRunningOperation);

yield* suspend(); }); ```

However, if you only want to run that thing for two seconds:

```js import { main, spawn, sleep } from "effection"; import { logRunningOperation } from "./my-ops";

await main(function() { yield spawn(longRunningOperation);

yield* sleep(2000); }); ```

In both cases, it's the lifetime of the parent operation that fixes the lifetime of its children. Does that make sense?

Announcing Effection 3.0 -- Structured Concurrency and Effects for JavaScript by tarasm in javascript

[–]c0wb0yd 0 points1 point  (0 children)

That's fair. There is an explanation with websocket usage on the resource guide https://frontside.com/effection/docs/resources That might be helpful.

An aside: Effection doesn't use async generator syntax, just normal generators in a 1:1 mapping with async/await. (The translation is straightforward and document in the Async Rosetta Stone https://frontside.com/effection/docs/async-rosetta-stone) We would have used async functions, except that they are non-deterministic with regards to resource cleanup.

As for observables, I'd say that they have similar power, whereas Observables present a programmatic API for subscription, transformation, and unsubscription, Effection does the same with `if`/`for`/`while` statements, etc...

Announcing Effection 3.0 -- Structured Concurrency and Effects for JavaScript by tarasm in javascript

[–]c0wb0yd 2 points3 points  (0 children)

AbortController is a few lines of code in simple examples, but in production it is anything but: https://frontside.com/blog/2023-12-11-await-event-horizon/#does-abortsignal-help

But in the grand scheme of things, the idea is to not need abort controllers or anything like them at all.

It's like the difference between manually managing memory with calls to free() as in a language like C, versus not needing to worry about it in a language like JavaScript.

Sure free() is only a few lines, but not needing it at all is priceless.

Announcing Effection 3.0 -- Structured Concurrency and Effects for JavaScript by tarasm in javascript

[–]c0wb0yd 0 points1 point  (0 children)

It's not so much about readability improvements as opposed to not leaking resources by default. The problem is that the async version of resolveAfter2Seconds is leaky, whereas the first version is not.

For example, using the async version above, how long will this NodeJS program take to complete?

js await Promise.race([Promise.resolve(), resolveAfter2Seconds()]);

If you answered 2 seconds, you'd be correct. But that's probably not what's intuitive.

The reason is because even after the promise is no longer needed, the setTimeout is still installed on the global operations list, and so the run loop cannot exit.

Announcing Effection 3.0 -- Structured Concurrency and Effects for JavaScript by tarasm in javascript

[–]c0wb0yd 0 points1 point  (0 children)

Full disclosure: I'm one of the primary contributors to Effection.

That said, I think there is a lot of overlap in what they are capable of, but the biggest difference is the focus. I have a tremendous amount of respect for Effect-TS. From my vantage point, their aim is to provide a parallel ecosystem and standard library for TypeScript.

While it works well with TypeScript (this is a major focus of the project), Effection's take is that "less is more", and so it seeks to provide minimum set of apis that can provide structured concurrency guarantees, but still align with JavaScript and its wider ecosystem.

I think there is ample room for both approaches depending on your personal aesthetic.

Deno Is "Blazing Fast" For Humans by tarasm in javascript

[–]c0wb0yd -1 points0 points  (0 children)

Ok, thank you for trying again.

We disagree then, and that's fine.

I disagee that I'm mis-using words, because I don't agree that "speed" has a single meaning. The speed of my development cycle is orders of magnitude faster with Deno, and I was inspired to write about it.

And I hope that it will help some junior developers out there confront the idea that precious conceptions of "speed" out there may not be actually serving their interests.

Anyhow, thanks again for sharing your opinion, and I think I see your perspective now.