Arc Forumnew | comments | leaders | submitlogin
The "attempted to cross a continuation barrier" error while using catch/throw
4 points by CatDancer 5556 days ago | 4 comments
I thought I'd post an explanation of this error in case anyone else ran into it and was googling for it.

In my case I have a web application that is parsing a bunch of request arguments and constructing an object out of them. If one of the arguments is invalid in some way, I want to return a 404 or similar HTTP response with an error code. A convenient way for me to program this is to use catch/throw, so that if an argument is invalid I break out of all the argument processing:

  (def parse-a (req throw)
    (if ...a is invalid... (do ...return 404 response... (throw nil)))
    ...parse and return a...)

  (catch
    (let foo (obj a (parse-a req throw)
                  b (parse-b req throw)
                  ...)
      ...process foo...
      ...return 200 OK response...))
However, when running in arc2, I get an MzScheme error when throw is called:

   Error: "continuation application: attempted to cross a continuation barrier"
Why? Here's the essence of the problem:

  arc> (catch (obj a (throw nil)))
  Error: "continuation application: attempted to cross a continuation barrier"
which happens because (obj a x) expands into

  (let tmp (table)
    (= (tmp 'a) x)
    tmp)
and Arc expands some complex invocations of = into code that uses atwith, the version of with wrapped in atomic. And MzScheme doesn't allow you to escape out of atomic with a continuation:

  arc> (catch (atomic (throw nil)))
  Error: "continuation application: attempted to cross a continuation barrier"
Here's the code in arc2/arc.arc that is expanding =:

  (def expand= (place val)
    (if (and (isa place 'sym) (~ssyntax place))
        `(set ,place ,val)
        (let (vars prev setter) (setforms place)
          (w/uniq g
            `(atwith ,(+ vars (list g val))
               (,setter ,g))))))
One option naturally is to avoid using Arc forms which invoke atomic when I'm using continuation escapes. But this isn't very pleasant, to have code that works fine until I start using catch/throw. Another fix is to replace the atwith in expand= with a plain with, and then my code starts working fine:

  arc> (catch (obj a (throw nil)))
  nil
Presumably this has a good chance of breaking something in Hacker News which is relying on the more complex setters to be atomic, but I haven't yet grokked the design of which Arc forms are made atomic.


2 points by rntz 5555 days ago | link

I have to say I'm not sure why '= is atomic. It does mean that modifications to complex data structures are more-or-less guaranteed not to put them into an inconsistent state, at least at the granularity level of a single assignment. However:

1) As a general principle, IMO, it should be the programmer's responsibility to avoid problems arising from concurrent use of objects by explicitly using concurrency primitives (mzscheme provides a host of these, and it's not too hard to write a wrapper which lifts them into arc, if someone hasn't already put one into Anarki). If they find this too much of a burden, then they can write some convenience macros.

2) Adding the overhead of 'atomic, which is implemented (and pretty much has to be implemented) using a global mutex, to every single assignment is just not okay.

3) It's not safe to assume that the granularity which the programmer needs in concurrency is precisely assignment-wide. Admittedly, the programmer can wrap whatever they want in 'atomic if the granularity is coarser, but then they're explicitly dealing with concurrency, as suggested in (1), and the atomicity of '= is moot.

-----

3 points by CatDancer 5555 days ago | link

I'm also thinking that having Arc make things atomic for us may be a bad idea, though perhaps for different reasons than you enumerate.

1) As a general principle, IMO, it should be the programmer's responsibility

I myself am quite happy to have responsibility taken off my hands, if the resulting code actually works. However...

2) Adding the overhead of 'atomic

If it worked, I wouldn't mind the overhead. (I haven't measured it, so I don't know if things being made atomic by Arc adds 10% to my program's execution time or 0.01% or 0.00001%) But the way to make a program fast is to profile it and fix the parts that are slow, not to leave out useful features in order to do global, unmeasured optimizations.

However...

I don't think it does work, which is probably part of what you're saying as well. Consider

  (readfile "foo.txt")
Reading a file might happen instantaneously if the operating system already has the file cached in memory; it might be slow if we're waiting for the hard disk to rotate into place to read it off of disk; it might take forever if the file is on a network filesystem.

Should I have the misfortune to say

  (obj a (readfile "foo.txt"))
this will work fine most of the time when reading the file is fast, but if the reading the file happens to be slow then I'll bring my entire web server to a halt waiting for the file to be read.

-----

3 points by rntz 5554 days ago | link

Yeah, it's reasons like that that lead me to point (1). When stuff is done for the programmer for nonobvious reasons, it leads to problems like this - there's nothing in the conceptual mechanics of (obj ...) that would make you think "oh, it's wrapped in a global mutex", and yet it is because assignment itself uses a global mutex. It's not so much that the language is "doing it for me" that's the issue with (1); that's fine. It's "explicitness" that's the problem: I want to know when I'm using a concurrency-controlling form like 'atomic. Otherwise, it's hard to reason about how my program is going to behave.

Also, this stuff about profiling to fix hotspots is mostly true, but hotspots come in many forms: tough algorithmic problems, folding or mapping some operation over a large data structure, etc. One form of hotspot is a common operation that isn't particularly localized in use. For example, some people have experimented with adding infix math to arc by having numbers cause infix parsing when they're called; they found this caused a slowdown of ~10%. I strongly suspect assignment falls into this category: it's used everywhere, it's a fundamental language feature, and making it faster will reap noticeable (albeit not overwhelming) benefits. However, I must admit I haven't tested this, so feel free to prove me wrong.

-----

2 points by CatDancer 5554 days ago | link

I agree with you on the first paragraph.

For the second, well, I wouldn't be surprised if someone who worked at optimizing Arc could make it run twice or maybe even ten times as fast, while still implementing the full specification. So if a particular language feature is actually useful, and happens to cause a 10% slowdown, I mostly don't worry about it myself... I suspect greater gains would be found from a systematic optimization approach.

Besides, most of the programs I write already run fast enough, so I wouldn't mind a 10% slowdown for a feature that was actually useful. Which, unless I'm presented with new information that changes my mind, I agree with you that Arc's implicit locking is not.

-----