From bcbe38d184eb5b4ee2169ac82bcafad9c0051520 Mon Sep 17 00:00:00 2001 From: Simon Ochsenreither Date: Sun, 19 May 2013 01:13:41 +0200 Subject: SI-7474 Parallel collections: End the exception handling madness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "What's wrong with an API which non-deterministically returns either type A or type B(Set(A, ...))?" This is pretty much what the exception handling behavior of the parallel collections does: If exceptions of type A occur, either an exception of type A or an exception of type B, wrapping multiple exceptions of A's, is returned. This behavior is incredibly broken and so unintuitive, that even people writing tests don't handle it correctly, as seen in test files/run/t5375.scala. Concerning “non-deterministic”: How many exceptions are observed before the operation is aborted depends on the machine, the available cores and hyper-threading, the operating system, the threadpool implementation and configuration, the size of the collection and the runtime itself. In fact, files/run/t5375.scala can be made to fail reproducible on both jdk7u and Avian, if we run on a single-core machine like in a virtual machine. With this change, we just pass the "first" exception which occurs. This is - consistent with the behaviour of sequential collections, - doesn't require users to know more about parallel collections than they already do ("elements might be processed out of order"), - in line with what Java 8 does. “Why don't we wrap everything in CompositeThrowables?” Even consistently returning CompositeThrowable doesn't make much sense (because we have fail-fast behaviour and don't wait until all tasks have finished or have thrown an exception). Therefore, there is no useful semantic in having a CompositeThrowable which returns "some" exceptions. I have done extensive research into C#'s approach (which is very similar to what Scala did until now, but even more messy) and the key observation from asking multiple C# developers is that not a single one had understood how PLINQ handled exceptions or could describe the semantics of it. As a consequence, either a) gather and return all exceptions in a CompositeThrowable or b) just return one, unwrapped, instead of non-deterministically wrapping a non-deterministic number of exceptions into a completely unrelated wrapper type. Considering that changing the parallel collection semantics in such a profound way as described in a) is out of question, b) is chosen. As soon as Scala targets Java > 7 Throwable#addSurpressed can be used to add further exceptions to the one which gets returned. This would allow passing more information to the caller without compromising the simplicity of the API. --- test/files/run/t5375.check | 2 +- test/files/run/t5375.scala | 23 ++++++----------------- 2 files changed, 7 insertions(+), 18 deletions(-) (limited to 'test') diff --git a/test/files/run/t5375.check b/test/files/run/t5375.check index 7d3002ffda..b1a57eeeec 100644 --- a/test/files/run/t5375.check +++ b/test/files/run/t5375.check @@ -1 +1 @@ -Composite throwable \ No newline at end of file +Runtime exception diff --git a/test/files/run/t5375.scala b/test/files/run/t5375.scala index fa5932ff89..826ecd841e 100644 --- a/test/files/run/t5375.scala +++ b/test/files/run/t5375.scala @@ -1,19 +1,8 @@ - - - -import collection.parallel.CompositeThrowable - - - -object Test { - - def main(args: Array[String]) { - val foos = (1 to 1000).toSeq - try { - foos.par.map(i => if (i % 37 == 0) sys.error("i div 37") else i) - } catch { - case CompositeThrowable(thr) => println("Composite throwable") - } +object Test extends App { + val foos = (1 to 1000).toSeq + try + foos.par.map(i => if (i % 37 == 0) sys.error("i div 37") else i) + catch { + case ex: RuntimeException => println("Runtime exception") } - } -- cgit v1.2.3