La concurrence sans s’emmêler les ficelles avec Project Loom

La concurrence sans s’emmêler les ficelles avec Project Loom

Donnée à Montpellier JUG (Nov. 2019)
Video: https://bit.ly/untangled-bordeaux-2019-video
Abstract:
Le domaine de la programmation concurrente utilise le champ lexical de la filature et nous met les nerfs en pelote avec des concepts qui s'entrelacent : fibers, threads, lightweight-threads, green threads, loom... Peut-être avez vous également entendu parler de coroutines ? Rassurez-vous, tout est lié.

Au fil de cette présentation (em)mêlée d'exemples de code, vous découvrirez le projet Loom qui a pour vocation d'apporter à la JVM des "mécanismes léger de concurrence", ainsi que des API plus haut niveau pour broder dessus.

Nous aborderons ensemble l'origine de Loom et ses récents prototypes. Vous vous apercevrez que son maillage va au delà de "simples" primitives et peut nous amener jusqu'à révolutionner la concurrence sur la JVM.

620e6dc3f97e1e871747838a1c07e3fc?s=128

Arnaud Bos

April 30, 2020
Tweet

Transcript

  1. 2.
  2. 3.
  3. 4.
  4. 5.
  5. 6.
  6. 7.
  7. 8.

    private Connection.Available getConnection(long eta, long wait, String token) { for(;;)

    { if (eta > MAX_ETA_MS) { throw new EtaExceededException(); } if (wait > 0) { Thread.sleep(wait); } println("Retrying download after " + wait + "ms wait."); Connection c = coordinator.requestConnection(token); if (c instanceof Connection.Available) { return (Connection.Available) c; } Connection.Unavailable unavail = (Connection.Unavailable) c; eta = unavail.getEta(); wait = unavail.getWait(); token = unavail.getToken(); } }
  8. 9.

    private Connection.Available getConnection(long eta, long wait, String token) { for(;;)

    { if (eta > MAX_ETA_MS) { throw new EtaExceededException(); } if (wait > 0) { Thread.sleep(wait); } println("Retrying download after " + wait + "ms wait."); Connection c = coordinator.requestConnection(token); if (c instanceof Connection.Available) { return (Connection.Available) c; } Connection.Unavailable unavail = (Connection.Unavailable) c; eta = unavail.getEta(); wait = unavail.getWait(); token = unavail.getToken(); } }
  9. 10.

    private Connection.Available getConnection(long eta, long wait, String token) { for(;;)

    { if (eta > MAX_ETA_MS) { throw new EtaExceededException(); } if (wait > 0) { Thread.sleep(wait); } println("Retrying download after " + wait + "ms wait."); Connection c = coordinator.requestConnection(token); if (c instanceof Connection.Available) { return (Connection.Available) c; } Connection.Unavailable unavail = (Connection.Unavailable) c; eta = unavail.getEta(); wait = unavail.getWait(); token = unavail.getToken(); } }
  10. 11.

    private Connection.Available getConnection(long eta, long wait, String token) { for(;;)

    { if (eta > MAX_ETA_MS) { throw new EtaExceededException(); } if (wait > 0) { Thread.sleep(wait); } println("Retrying download after " + wait + "ms wait."); Connection c = coordinator.requestConnection(token); if (c instanceof Connection.Available) { return (Connection.Available) c; } Connection.Unavailable unavail = (Connection.Unavailable) c; eta = unavail.getEta(); wait = unavail.getWait(); token = unavail.getToken(); } }
  11. 12.

    private Connection.Available getConnection(long eta, long wait, String token) { for(;;)

    { if (eta > MAX_ETA_MS) { throw new EtaExceededException(); } if (wait > 0) { Thread.sleep(wait); } println("Retrying download after " + wait + "ms wait."); Connection c = coordinator.requestConnection(token); if (c instanceof Connection.Available) { return (Connection.Available) c; } Connection.Unavailable unavail = (Connection.Unavailable) c; eta = unavail.getEta(); wait = unavail.getWait(); token = unavail.getToken(); } }
  12. 13.

    private Connection.Available getConnection(long eta, long wait, String token) { for(;;)

    { if (eta > MAX_ETA_MS) { throw new EtaExceededException(); } if (wait > 0) { Thread.sleep(wait); } println("Retrying download after " + wait + "ms wait."); Connection c = coordinator.requestConnection(token); if (c instanceof Connection.Available) { return (Connection.Available) c; } Connection.Unavailable unavail = (Connection.Unavailable) c; eta = unavail.getEta(); wait = unavail.getWait(); token = unavail.getToken(); } }
  13. 14.

    private Connection.Available getConnection(long eta, long wait, String token) { for(;;)

    { if (eta > MAX_ETA_MS) { throw new EtaExceededException(); } if (wait > 0) { Thread.sleep(wait); } println("Retrying download after " + wait + "ms wait."); Connection c = coordinator.requestConnection(token); if (c instanceof Connection.Available) { return (Connection.Available) c; } Connection.Unavailable unavail = (Connection.Unavailable) c; eta = unavail.getEta(); wait = unavail.getWait(); token = unavail.getToken(); } }
  14. 15.
  15. 16.
  16. 17.
  17. 18.
  18. 19.
  19. 20.

    public static InputStream blockingRequest(String url, String headers) throws IOException {

    URL uri = new URL(url); SocketAddress serverAddress = new InetSocketAddress(uri.getHost(), uri.getPort() SocketChannel channel = SocketChannel.open(serverAddress); ByteBuffer buffer = ByteBuffer.wrap((headers + "Host: " + uri.getHost() + "\r\n\ do { channel.write(buffer); } while(buffer.hasRemaining()); return channel.socket().getInputStream(); }
  20. 21.
  21. 22.
  22. 23.
  23. 24.
  24. 25.
  25. 26.
  26. 28.
  27. 29.

    private void getConnection(long eta, long wait, String token, CompletionHandler<Connection.Available> handler)

    { if (eta > MAX_ETA_MS) { if (handler!=null) handler.failed(new EtaExceededException()); } boundedServiceExecutor.schedule(() -> { println("Retrying download after " + wait + "ms wait."); coordinator.requestConnection(token, new CompletionHandler<>() { @Override public void completed(Connection c) { if (c instanceof Connection.Available) { if (handler!=null) handler.completed((Connection.Available) c); } else { Connection.Unavailable unavail = (Connection.Unavailable) c; getConnection( unavail.getEta(), unavail.getWait(), unavail.getToken(), handler); } } @Override public void failed(Throwable t) { ...
  28. 30.

    private void getConnection(long eta, long wait, String token, CompletionHandler<Connection.Available> handler)

    { if (eta > MAX_ETA_MS) { if (handler!=null) handler.failed(new EtaExceededException()); } boundedServiceExecutor.schedule(() -> { println("Retrying download after " + wait + "ms wait."); coordinator.requestConnection(token, new CompletionHandler<>() { @Override public void completed(Connection c) { if (c instanceof Connection.Available) { if (handler!=null) handler.completed((Connection.Available) c); } else { Connection.Unavailable unavail = (Connection.Unavailable) c; getConnection( unavail.getEta(), unavail.getWait(), unavail.getToken(), handler); } } @Override public void failed(Throwable t) { ...
  29. 31.

    private void getConnection(long eta, long wait, String token, CompletionHandler<Connection.Available> handler)

    { if (eta > MAX_ETA_MS) { if (handler!=null) handler.failed(new EtaExceededException()); } boundedServiceExecutor.schedule(() -> { println("Retrying download after " + wait + "ms wait."); coordinator.requestConnection(token, new CompletionHandler<>() { @Override public void completed(Connection c) { if (c instanceof Connection.Available) { if (handler!=null) handler.completed((Connection.Available) c); } else { Connection.Unavailable unavail = (Connection.Unavailable) c; getConnection( unavail.getEta(), unavail.getWait(), unavail.getToken(), handler); } } @Override public void failed(Throwable t) { ...
  30. 32.

    private void getConnection(long eta, long wait, String token, CompletionHandler<Connection.Available> handler)

    { if (eta > MAX_ETA_MS) { if (handler!=null) handler.failed(new EtaExceededException()); } boundedServiceExecutor.schedule(() -> { println("Retrying download after " + wait + "ms wait."); coordinator.requestConnection(token, new CompletionHandler<>() { @Override public void completed(Connection c) { if (c instanceof Connection.Available) { if (handler!=null) handler.completed((Connection.Available) c); } else { Connection.Unavailable unavail = (Connection.Unavailable) c; getConnection( unavail.getEta(), unavail.getWait(), unavail.getToken(), handler); } } @Override public void failed(Throwable t) { ...
  31. 33.

    private void getConnection(long eta, long wait, String token, CompletionHandler<Connection.Available> handler)

    { if (eta > MAX_ETA_MS) { if (handler!=null) handler.failed(new EtaExceededException()); } boundedServiceExecutor.schedule(() -> { println("Retrying download after " + wait + "ms wait."); coordinator.requestConnection(token, new CompletionHandler<>() { @Override public void completed(Connection c) { if (c instanceof Connection.Available) { if (handler!=null) handler.completed((Connection.Available) c); } else { Connection.Unavailable unavail = (Connection.Unavailable) c; getConnection( unavail.getEta(), unavail.getWait(), unavail.getToken(), handler); } } @Override public void failed(Throwable t) { ...
  32. 34.
  33. 35.
  34. 36.
  35. 37.

    public static InputStream blockingRequest(String url, String headers) throws IOException {

    URL uri = new URL(url); SocketAddress serverAddress = new InetSocketAddress(uri.getHost(), uri.getPort() SocketChannel channel = SocketChannel.open(serverAddress); ByteBuffer buffer = ByteBuffer.wrap((headers + "Host: " + uri.getHost() + "\r\n\ do { channel.write(buffer); } while(buffer.hasRemaining()); return channel.socket().getInputStream(); }
  36. 38.
  37. 39.
  38. 40.
  39. 41.

    public static void asyncNonBlockingRequest(ExecutorService executor, String url, String headers, RequestHandler

    handler) { executor.submit(() -> { try { println("Starting request to " + url); URL uri = new URL(url); SocketAddress serverAddress = new InetSocketAddress(uri.getHost(), 80); AsynchronousSocketChannel channel = AsynchronousSocketChannel.open(group); channel.connect(serverAddress, null, new CompletionHandler<Void>() { @Override public void completed(Void result, Void attachment) { ByteBuffer headersBuffer = ByteBuffer.wrap((headers + "Host: " + uri.getHost())); ByteBuffer responseBuffer = ByteBuffer.allocate(1024); channel.write(headersBuffer, headersBuffer, new CompletionHandler<>() { @Override public void completed(Integer written, ByteBuffer attachment) { if (attachment.hasRemaining()) { channel.write(attachment, attachment, this); } else { channel.read(responseBuffer, responseBuffer, new CompletionHandler<>() { @Override public void completed(Integer read, ByteBuffer attachment) { // More // this // way // ==>
  40. 42.

    private Connection.Available getConnection(long eta, long wait, String token) { for(;;)

    { if (eta > MAX_ETA_MS) { throw new EtaExceededException(); } if (wait > 0) { Thread.sleep(wait); } println("Retrying download after " + wait + "ms wait."); Connection c = coordinator.requestConnection(token); if (c instanceof Connection.Available) { return (Connection.Available) c; } Connection.Unavailable unavail = (Connection.Unavailable) c; eta = unavail.getEta(); wait = unavail.getWait(); token = unavail.getToken(); } }
  41. 43.

    public static void asyncNonBlockingRequest(ExecutorService executor, String url, String headers, RequestHandler

    handler) { executor.submit(() -> { try { println("Starting request to " + url); URL uri = new URL(url); SocketAddress serverAddress = new InetSocketAddress(uri.getHost(), 80); AsynchronousSocketChannel channel = AsynchronousSocketChannel.open(group); channel.connect(serverAddress, null, new CompletionHandler<Void>() { @Override public void completed(Void result, Void attachment) { ByteBuffer headersBuffer = ByteBuffer.wrap((headers + "Host: " + uri.getHost())); ByteBuffer responseBuffer = ByteBuffer.allocate(1024); channel.write(headersBuffer, headersBuffer, new CompletionHandler<>() { @Override public void completed(Integer written, ByteBuffer attachment) { if (attachment.hasRemaining()) { channel.write(attachment, attachment, this); } else { channel.read(responseBuffer, responseBuffer, new CompletionHandler<>() { @Override public void completed(Integer read, ByteBuffer attachment) { // More // this // way // ==>
  42. 44.
  43. 45.
  44. 46.
  45. 47.
  46. 49.
  47. 50.
  48. 52.

    private Mono<Connection.Available> getConnection(long eta, long wait, String token) { AtomicLong

    etaRef = new AtomicLong(eta); AtomicLong waitRef = new AtomicLong(wait); AtomicReference<String> tokenRef = new AtomicReference<>(token); return Mono.defer(() -> { if (etaRef.get() > MAX_ETA_MS) { return Mono.error(new EtaExceededException()); } return Mono.delay(Duration.ofMillis(waitRef.get())) .flatMap(i -> coordinator.requestConnection(tokenRef.get())); }).flatMap(c -> { if (c instanceof Connection.Available) { return Mono.just((Connection.Available) c); } else { Connection.Unavailable unavail = (Connection.Unavailable) c; etaRef.set(unavail.getEta()); waitRef.set(unavail.getWait()); tokenRef.set(unavail.getToken()); return Mono.empty(); } }).repeatWhenEmpty(Repeat .onlyIf(ctx -> true) .doOnRepeat(ctx -> println(waitRef.get() + ", " + etaRef.get() + ", " + tokenRef.get()))); }
  49. 53.

    private Mono<Connection.Available> getConnection(long eta, long wait, String token) { AtomicLong

    etaRef = new AtomicLong(eta); AtomicLong waitRef = new AtomicLong(wait); AtomicReference<String> tokenRef = new AtomicReference<>(token); return Mono.defer(() -> { if (etaRef.get() > MAX_ETA_MS) { return Mono.error(new EtaExceededException()); } return Mono.delay(Duration.ofMillis(waitRef.get())) .flatMap(i -> coordinator.requestConnection(tokenRef.get())); }).flatMap(c -> { if (c instanceof Connection.Available) { return Mono.just((Connection.Available) c); } else { Connection.Unavailable unavail = (Connection.Unavailable) c; etaRef.set(unavail.getEta()); waitRef.set(unavail.getWait()); tokenRef.set(unavail.getToken()); return Mono.empty(); } }).repeatWhenEmpty(Repeat .onlyIf(ctx -> true) .doOnRepeat(ctx -> println(waitRef.get() + ", " + etaRef.get() + ", " + tokenRef.get()))); }
  50. 54.
  51. 55.
  52. 56.
  53. 57.
  54. 58.
  55. 59.
  56. 60.
  57. 61.
  58. 62.

    class StateMachineIterator implements Iterator<String> { private int state; private int

    i; public String next() { switch(state) { case 0: state=1; return "A"; case 1: state=2; i=0; return "B"; case 2: if(i == 3) state = 3; return "C" + (i++); case 3: state=4; return "D"; case 4: state=5; return "E"; default: throw new NoSuchElementException(); } } public boolean hasNext() { return state < 5; } public void remove() { throw new UnsupportedOperationException("Not supported"); } }
  59. 63.

    class StateMachineIterator implements Iterator<String> { private int state; private int

    i; public String next() { switch(state) { case 0: state=1; return "A"; case 1: state=2; i=0; return "B"; case 2: if(i == 3) state = 3; return "C" + (i++); case 3: state=4; return "D"; case 4: state=5; return "E"; default: throw new NoSuchElementException(); } } public boolean hasNext() { return state < 5; } public void remove() { throw new UnsupportedOperationException("Not supported"); } }
  60. 64.

    class StateMachineIterator implements Iterator<String> { private int state; private int

    i; public String next() { switch(state) { case 0: state=1; return "A"; case 1: state=2; i=0; return "B"; case 2: if(i == 3) state = 3; return "C" + (i++); case 3: state=4; return "D"; case 4: state=5; return "E"; default: throw new NoSuchElementException(); } } public boolean hasNext() { return state < 5; } public void remove() { throw new UnsupportedOperationException("Not supported"); } }
  61. 65.

    class StateMachineIterator implements Iterator<String> { private int state; private int

    i; public String next() { switch(state) { case 0: state=1; return "A"; case 1: state=2; i=0; return "B"; case 2: if(i == 3) state = 3; return "C" + (i++); case 3: state=4; return "D"; case 4: state=5; return "E"; default: throw new NoSuchElementException(); } } public boolean hasNext() { return state < 5; } public void remove() { throw new UnsupportedOperationException("Not supported"); } }
  62. 66.

    class StateMachineIterator implements Iterator<String> { private int state; private int

    i; public String next() { switch(state) { case 0: state=1; return "A"; case 1: state=2; i=0; return "B"; case 2: if(i == 3) state = 3; return "C" + (i++); case 3: state=4; return "D"; case 4: state=5; return "E"; default: throw new NoSuchElementException(); } } public boolean hasNext() { return state < 5; } public void remove() { throw new UnsupportedOperationException("Not supported"); } }
  63. 67.
  64. 68.
  65. 69.
  66. 70.

    private Connection.Available getConnection(long eta, long wait, String token) { for(;;)

    { if (eta > MAX_ETA_MS) { throw new EtaExceededException(); } if (wait > 0) { Thread.sleep(wait); } println("Retrying download after " + wait + "ms wait."); Connection c = coordinator.requestConnection(token); if (c instanceof Connection.Available) { return (Connection.Available) c; } Connection.Unavailable unavail = (Connection.Unavailable) c; eta = unavail.getEta(); wait = unavail.getWait(); token = unavail.getToken(); } }
  67. 71.

    Connection.Available conn = getConnection(); Runnable pulse = makePulse(conn); Fiber<Object> f

    = null; try (InputStream content = gateway.downloadThingy()) { f = FiberScope.background().schedule(pulse); ignoreContent(content); } catch (IOException e) { err("Download failed."); throw e; } finally { if (f!=null) { f.cancel(); } }
  68. 72.

    Connection.Available conn = getConnection(); Runnable pulse = makePulse(conn); Fiber<Object> f

    = null; try (InputStream content = gateway.downloadThingy()) { f = FiberScope.background().schedule(pulse); ignoreContent(content); } catch (IOException e) { err("Download failed."); throw e; } finally { if (f!=null) { f.cancel(); } }
  69. 73.

    unboundedServiceExecutor = Executors.newUnboundedVirtualThreadExecutor(); ... Connection.Available conn = getConnection(); Runnable pulse

    = makePulse(conn); Future<Object> f = null; try (InputStream content = gateway.downloadThingy()) { f = unboundedServiceExecutor.submit(pulse); ignoreContent(content); } catch (IOException e) { err("Download failed."); throw e; } finally { if (f!=null) { f.cancel(); } }
  70. 74.
  71. 75.
  72. 76.
  73. 77.
  74. 78.
  75. 79.
  76. 80.
  77. 81.
  78. 82.
  79. 85.

    Connection.Available conn = getConnection(); Runnable pulse = makePulse(conn); Future<Object> f

    = null; try (InputStream content = gateway.downloadThingy()) { f = unboundedServiceExecutor.submit(pulse); ignoreContent(content); } catch (IOException e) { err("Download failed."); throw e; } finally { if (f!=null) { f.cancel(); } }
  80. 86.

    try (FiberScope scope = FiberScope.open(CANCEL_AT_CLOSE, PROPAGATE_CANCEL)) { var conn =

    getConnection(); try (InputStream content = gateway.downloadThingy()) { Runnable pulse = makePulse(conn, i); scope.schedule(pulse); ignoreContent(content); } } catch (IOException e) { err("Download failed."); throw e; }
  81. 87.

    try (FiberScope scope = FiberScope.open(CANCEL_AT_CLOSE, PROPAGATE_CANCEL)) { var conn =

    getConnection(); try (InputStream content = gateway.downloadThingy()) { Runnable pulse = makePulse(conn, i); scope.schedule(pulse); ignoreContent(content); } } catch (IOException e) { err("Download failed."); throw e; }
  82. 88.
  83. 89.
  84. 90.

    Papers and Webpages Flynn, Michael J. - Some Computer Organizations

    and Their Effectiveness Haynes, Christopher T. - Logic Continuations Wand, Mitchell - Continuation-based Multiprocessing Loitsch, Florian - Exceptional Continuations in JavaScript Long, James - What's In A Continuation Reynolds, John C. - The Discoveries of Continuations Clinger, Hartheimer, Ost - Implementation Strategies for Continuations Haynes, Friedman, Wand - Obtaining Coroutines with Continuations Pressler, Ron - Why Continuations are Coming to Java Pressler, Ron - https://mail.openjdk.java.net/pipermail/loom-dev/2019-November/000876.html Repos https://github.com/forax/loom-fiber/ https://github.com/arnaudbos/untangled/
  85. 91.

    Papers and Webpages Flynn, Michael J. - Some Computer Organizations

    and Their Effectiveness Haynes, Christopher T. - Logic Continuations Wand, Mitchell - Continuation-based Multiprocessing Loitsch, Florian - Exceptional Continuations in JavaScript Long, James - What's In A Continuation Reynolds, John C. - The Discoveries of Continuations Clinger, Hartheimer, Ost - Implementation Strategies for Continuations Haynes, Friedman, Wand - Obtaining Coroutines with Continuations Pressler, Ron - Why Continuations are Coming to Java Pressler, Ron - https://mail.openjdk.java.net/pipermail/loom-dev/2019-November/000876.html Repos https://github.com/forax/loom-fiber/ https://github.com/arnaudbos/untangled/
  86. 92.