Provide explicit ways to prevent concurrency

@hugetim I apologize if I misunderstood your original post–to control concurrency implied to me enabling it further (which I think is a bad idea for Anvil), as well as preventing it. Prevent concurrency is a more precise title, but I don’t think this is practical. Anvil already prevents concurrency as much as it can, and I don’t think it can prevent it any further. I’ll try to explain, and I’ll try to be helpful, rather than just taking pot shots at Ryan Dahl.

JavaScript was originally designed to run on computers with only one core (single-threaded CPUs). In that context, if you block the main code path, the computer will hang. So JavaScript was designed as a non-blocking language, using events and callbacks for anything that might take time to complete. Even today, in a modern browser, your web app runs as a single thread. You can put your script in a tight loop, and this will lock up the tab, but you cannot write any statement that actually waits for something to happen.

Although it’s a slight oversimplification, you can think of each JavaScript statement as atomic. Because there’s only one thread, nothing else in your program can interrupt it until it’s finished. In this sense there is no real concurrency in JavaScript, and you never have to worry about realtime issues, like data corruption. You just need to worry about order of operation, due to the multiple code paths created by the ubiquitous callbacks.

Now, Anvil compiles a single Python statement into many JavaScript statements. For example, this line:

print "hello world"

compiles into this:

var $scope0 = (function($modname) {
    var $blk = 0,
        $exc = [],
        $gbl = {},
        $loc = $gbl,
        $err = undefined;
    $gbl.__name__ = $modname;
    Sk.globals = $gbl;
    try {
        while (true) {
            try {
                switch ($blk) {
                case 0:
                    /* --- module entry --- */
                    //
                    // line 1:
                    // print "hello world"
                    // ^
                    //
                    Sk.currLineNo = 1;
                    Sk.currColNo = 0

                    Sk.currFilename = './simple.py';

                    var $str1 = new Sk.builtins['str']('hello world');
                    Sk.misceval.print_(new Sk.builtins['str']($str1).v);
                    Sk.misceval.print_("\n");
                    return $loc;
                    throw new Sk.builtin.SystemError('internal error: unterminated block');
                }
            } catch (err) {
                if ($exc.length > 0) {
                    $err = err;
                    $blk = $exc.pop();
                    continue;
                } else {
                    throw err;
                }
            }
        }
    } catch (err) {
        if (err instanceof Sk.builtin.SystemExit && !Sk.throwSystemExit) {
            Sk.misceval.print_(err.toString() + '\n');
            return $loc;
        } else {
            throw err;
        }
    }
});

The resulting function, however, is executed by the JavaScript interpreter on a single trip through the event loop (someone like @stucork might be able to verify this), so each Python statement is also, for practical purposes, atomic. In other words, there’s no real concurrency in Anvil Python, either.

Except that that’s not quite true. Python is a synchronous language that always waits for I/O to complete before moving on. In other words, it blocks. How do you compile a blocking language into a non-blocking language? They use a trick, called a Suspense. Essentially, they suspend the Python statement until the I/O completes, and then resume it where it left off. In the meantime, however, other things can happen, and this is where the order of operations problem bleeds through into your Anvil app. UI events, which can happen at any time, also cause this problem.

What you want is for Anvil to stop any events from happening, or any alternate code paths from executing, until an entire ‘critical section’ defined by you completes. But there’s no way to stop the normal flow of execution in JavaScript. What you can do is to write a JavaScript function to do the uninterruptable work, and call it from Anvil.

And you can take action, at the start of an event, to prevent conflicting behavior. Let me take your example from your last post: You have a timer running, and a button the user can click. They are essentially in a race. When the button is pressed, the first thing you should do is to disable the timer, and then disable the button. Now you can proceed safely. If the timer fires first, you should immediately block the action of the button, or disable it; and don’t schedule the next tick of the timer until you’re done with whatever you’re going to do in this case. More complicated scenarios require more sophisticated logic, but the basic idea will work, if you think it through.

This is not the end of the story. Browsers are evolving. You can make any JavaScript function asynchronous with Promises. You can schedule things to complete before the next iteration of the event loop with Microtasks. You can create real background tasks with Service Workers. But all this requires even more coordination–you can’t get away from that.

Personally, I’d love to see browsers natively support many different languages, including and especially Python. I don’t see why CPYTHON can’t be baked into a browser. If I were a younger man I would write a browser with Python in it, with real threads for concurrency. That would solve this any many other problems.

John

4 Likes