Upgrade to Pro — share decks privately, control downloads, hide ads and more …

DevoxxFR 2018: Spéléo Reactor

DevoxxFR 2018: Spéléo Reactor

Simon Baslé

April 18, 2018
Tweet

More Decks by Simon Baslé

Other Decks in Programming

Transcript

  1. Spéléo Reactor
    @simonbasle
    Dans les profondeurs de la
    Programmation Réactive

    View Slide

  2. Au Programme

    View Slide

  3. ● introduction / reactive streams
    ● map
    ● la fusion
    ● concatMap
    ● la “drain loop”

    View Slide

  4. Introduction

    View Slide

  5. Programmation
    Réactive

    View Slide

  6. “Composer des séquences
    asynchrones,
    basées sur les événements
    avec des opérateurs non-bloquants”

    View Slide

  7. Reactive Streams

    View Slide

  8. Reactive Streams
    standard d’interopérabilité
    JDK 9 Flow
    Publisher
    Subscriber
    Subscription

    View Slide

  9. Standard d’interopérabilité

    View Slide

  10. Aussi dans JDK 9
    (Flow)

    View Slide

  11. Publisher
    produit la donnée

    View Slide

  12. Subscriber
    consomme la donnée

    View Slide

  13. Publisher Subscriber
    pousse des
    éléments
    produit consomme
    feedback

    View Slide

  14. subscribe(Subscriber)
    Publisher Subscriber

    View Slide

  15. onNext(T)
    onError(Throwable)
    onComplete()
    Publisher Subscriber

    View Slide

  16. Publisher Subscriber
    pousse des
    éléments
    produit consomme
    feedback
    “0 à N onNext suivi d’au plus 1
    (onComplete ou onError)”

    View Slide

  17. onNext(T)
    onError(Throwable)
    onComplete()
    Publisher Subscriber

    View Slide

  18. + onSubscribe(Subscription)
    onNext(T)
    onError(Throwable)
    onComplete()
    Publisher Subscriber

    View Slide

  19. Subscription
    requête et annulation

    View Slide

  20. Subscriber au Publisher:
    “je suis disposé à traiter N éléments, envoie
    les-moi dès que possible”

    View Slide

  21. C’est la Backpressure

    View Slide

  22. à cet effet
    le Publisher fourni une Subscription
    au Subscriber

    View Slide

  23. grâce à elle, le Subscriber peut notifier
    de sa capacité de traitement via
    Subscription#request(long)

    View Slide

  24. Publisher Subscriber
    pousse des
    éléments
    produit consomme
    request(n)
    backpressure

    View Slide

  25. Modèle dit
    “Asynchronous Push-Pull”

    View Slide

  26. Modèle dit
    “Asynchronous Push-Pull”

    View Slide

  27. Modèle dit
    “Asynchronous Push-Pull”
    Le Subscriber demande N éléments
    au Publisher (notamment à la souscription)

    View Slide

  28. Modèle dit
    “Asynchronous Push-Pull”

    View Slide

  29. Modèle dit
    “Asynchronous Push-Pull”
    Le Publisher envoie au max le nombre
    demandé, en asynchrone (au fil de l’eau)

    View Slide

  30. Diagramme à billes
    (“marble diagram”)

    View Slide

  31. View Slide

  32. onNext

    View Slide

  33. onComplete

    View Slide

  34. la séquence

    View Slide

  35. direction du
    “temps”

    View Slide

  36. plus de délai
    entre ces deux
    onNext

    View Slide

  37. une séquence qui tombe en erreur
    (onError)

    View Slide

  38. Contraintes intéressantes
    de Concurrence

    View Slide

  39. les onNext, ainsi que les
    événements terminaux, sont en série

    View Slide

  40. les onNext, ainsi que les
    événements terminaux, sont en série
    ie. pas d’appel parallèle à onNext
    pendant un onNext

    View Slide

  41. request & cancel peuvent
    se produire en parallèle
    (de onNext et d’eux même)
    req(2)
    req(7) req(1)

    View Slide

  42. can I have an
    API though?
    Publisher Subscriber

    View Slide

  43. Publisher Subscriber

    View Slide

  44. Reactor

    View Slide

  45. Reactor
    et son vocabulaire d’opérateurs
    Reactive Streams de bout en bout
    Flux
    Mono
    Riche API d’opérateurs

    View Slide

  46. Tous les types sont des Publisher

    View Slide

  47. Flux
    pour 0-N éléments

    View Slide

  48. View Slide

  49. Mono
    pour 0-1 élément

    View Slide

  50. valué vide en erreur

    View Slide

  51. Large vocabulaire
    d’Opérateurs

    View Slide

  52. View Slide

  53. Autres Concepts
    Importants

    View Slide

  54. Autres Concepts
    Importants
    souscription vers le haut, données
    vers le bas
    rien ne se passe avant
    `subscribe()`
    enchaîner les opérateurs

    View Slide

  55. souscription de bas en haut
    données de haut en bas

    View Slide

  56. Flux/Mono
    generator
    operator
    operator
    operator

    View Slide

  57. Flux/Mono
    generator
    Subscriber
    operator
    operator
    operator

    View Slide

  58. Flux/Mono
    generator
    Subscriber
    operator
    operator
    operator
    état propre à chaque
    Subscription
    Sub
    Sub
    Sub

    View Slide

  59. Flux/Mono
    generator
    Subscriber
    operator
    operator
    operator
    le flux de données
    commence
    Sub
    Sub
    Sub

    View Slide

  60. les opérateurs sont à la fois
    des
    Publisher et des Subscriber

    View Slide

  61. rien ne se passe tant qu’on n’a pas
    subscribe( )

    View Slide

  62. (dans la plupart des cas)
    la source des données ne
    commence à émettre que lors
    de la souscription

    View Slide

  63. crée une chaine de
    subscribers dédiés
    propage la 1ère requête, qui
    “démarre” la source

    View Slide

  64. enchaîner les opérateurs

    View Slide

  65. chaque opérateur retourne
    une nouvelle instance qui
    décore l’opérateur précédent.

    View Slide

  66. il faut donc enchaîner les
    appels (le style “fluent API”)

    View Slide

  67. Première descente:
    map

    View Slide

  68. L’opérateur map
    que fait l’opérateur ?
    à quoi ressemble le code ?

    View Slide

  69. que fait l’opérateur?

    View Slide

  70. Flux map(Function f)

    View Slide

  71. Flux map(Function f)

    View Slide

  72. Flux map(Function f)

    View Slide

  73. Flux map(Function f)

    View Slide

  74. Flux map(Function f)

    View Slide

  75. Flux map(Function f)

    View Slide

  76. T R
    map( )

    View Slide

  77. T R
    map( )

    View Slide

  78. à quoi ressemble le code?

    View Slide

  79. le Publisher
    l’opérateur, instancié par la méthode Flux#map

    View Slide

  80. final class FluxMap extends FluxOperator {
    final Function super T, ? extends R> mapper;
    FluxMap(Flux extends T> source,
    Function super T, ? extends R> mapper) {
    super(source);
    this.mapper = Objects.requireNonNull(mapper, "mapper");
    }
    @Override
    @SuppressWarnings("unchecked")
    public void subscribe(CoreSubscriber super R> actual) {
    source.subscribe(new MapSubscriber<>(actual, mapper));
    }
    }

    View Slide

  81. final class FluxMap extends FluxOperator {
    final Function super T, ? extends R> mapper;
    FluxMap(Flux extends T> source,
    Function super T, ? extends R> mapper) {
    super(source);
    this.mapper = Objects.requireNonNull(mapper, "mapper");
    }
    @Override
    @SuppressWarnings("unchecked")
    public void subscribe(CoreSubscriber super R> actual) {
    source.subscribe(new MapSubscriber<>(actual, mapper));
    }
    }

    View Slide

  82. le MapSubscriber

    View Slide

  83. static final class MapSubscriber
    implements InnerOperator {
    final CoreSubscriber super R> actual;
    final Function super T, ? extends R> mapper;
    boolean done;
    Subscription s;
    MapSubscriber(CoreSubscriber super R> actual,
    Function super T, ? extends R> mapper) {
    this.actual = actual;
    this.mapper = mapper;
    }
    ...

    View Slide

  84. static final class MapSubscriber
    implements InnerOperator {
    final CoreSubscriber super R> actual;
    final Function super T, ? extends R> mapper;
    boolean done;
    Subscription s;
    MapSubscriber(CoreSubscriber super R> actual,
    Function super T, ? extends R> mapper) {
    this.actual = actual;
    this.mapper = mapper;
    }

    View Slide

  85. final CoreSubscriber super R> actual;
    Subscription s;
    ...
    @Override
    public void onSubscribe(Subscription s) {
    if (Operators.validate(this.s, s)) {
    this.s = s;
    actual.onSubscribe(this);
    }
    }

    View Slide

  86. final CoreSubscriber super R> actual;
    boolean done;
    ...
    @Override
    public void onComplete() {
    if (done) {
    return;
    }
    done = true;
    actual.onComplete();
    }

    View Slide

  87. final CoreSubscriber super R> actual;
    boolean done;
    ...
    @Override
    public void onError(Throwable t) {
    if (done) {
    return;
    }
    done = true;
    actual.onError(t);
    }

    View Slide

  88. static final class MapSubscriber
    implements InnerOperator {
    ...
    Subscription s;
    ...
    @Override
    public void request(long n) {
    s.request(n);
    }
    @Override
    public void cancel() {
    s.cancel();
    }
    }

    View Slide

  89. son onNext
    le coeur de métier de l’opérateur

    View Slide

  90. @Override
    public void onNext(T t) {
    if (done) {
    return;
    }
    R v;
    try {
    v = Objects.requireNonNull(mapper.apply(t),
    "The mapper returned a null value.");
    }
    catch (Throwable e) {
    onError(e);
    return;
    }
    actual.onNext(v);
    }

    View Slide

  91. @Override
    public void onNext(T t) {
    if (done) {
    return;
    }
    R v;
    try {
    v = Objects.requireNonNull(mapper.apply(t),
    "The mapper returned a null value.");
    }
    catch (Throwable e) {
    onError(e);
    return;
    }
    actual.onNext(v);
    }

    View Slide

  92. pause
    15min

    View Slide

  93. La Fusion
    map peut être fusionné
    comment la fusion est-elle
    implémentée ?
    quel impact sur la chaîne
    d’opérateurs ?

    View Slide

  94. map peut être
    Fusionné

    View Slide

  95. le subscriber est aussi une
    Queue
    réutilisée par chaque
    opérateur fusionné en
    dessous

    View Slide

  96. remplace des queues internes
    peut remonter jusqu’à la source
    (eg. Flux.fromIterable)

    View Slide

  97. opérateur source opérateur 2 opérateur 3
    sans fusion

    View Slide

  98. opérateur source opérateur 2 opérateur 3
    avec fusion

    View Slide

  99. Impact sur le code
    interface Fuseable

    View Slide

  100. final class FluxMapFuseable extends FluxOperator
    implements Fuseable {
    final Function super T, ? extends R> mapper;
    FluxMapFuseable(Flux extends T> source,
    Function super T, ? extends R> mapper) {
    super(source);
    this.mapper = Objects.requireNonNull(mapper, "mapper");
    }
    @Override
    public void subscribe(CoreSubscriber super R> actual) {
    source.subscribe(new MapFuseableSubscriber<>(actual, mapper));
    }

    View Slide

  101. final class FluxMapFuseable extends FluxOperator
    implements Fuseable {
    final Function super T, ? extends R> mapper;
    FluxMapFuseable(Flux extends T> source,
    Function super T, ? extends R> mapper) {
    super(source);
    this.mapper = Objects.requireNonNull(mapper, "mapper");
    }
    @Override
    public void subscribe(CoreSubscriber super R> actual) {
    source.subscribe(new MapFuseableSubscriber<>(actual, mapper));
    }

    View Slide

  102. static final class MapFuseableSubscriber
    implements InnerOperator,
    QueueSubscription {
    final CoreSubscriber super R> actual;
    final Function super T, ? extends R> mapper;
    boolean done;
    QueueSubscription s;
    int sourceMode;
    MapFuseableSubscriber(CoreSubscriber super R> actual,
    Function super T, ? extends R> mapper) {
    this.actual = actual;
    this.mapper = mapper;
    }

    View Slide

  103. static final class MapFuseableSubscriber
    implements InnerOperator,
    QueueSubscription {
    final CoreSubscriber super R> actual;
    final Function super T, ? extends R> mapper;
    boolean done;
    QueueSubscription s;
    int sourceMode;
    MapFuseableSubscriber(CoreSubscriber super R> actual,
    Function super T, ? extends R> mapper) {
    this.actual = actual;
    this.mapper = mapper;
    }

    View Slide

  104. négociation de la fusion

    View Slide

  105. @Override
    public int requestFusion(int requestedMode) {
    int m;
    if ((requestedMode & Fuseable.THREAD_BARRIER) != 0) {
    return Fuseable.NONE;
    }
    else {
    m = s.requestFusion(requestedMode);
    }
    sourceMode = m;
    return m;
    }

    View Slide

  106. @Override
    public int requestFusion(int requestedMode) {
    int m;
    if ((requestedMode & Fuseable.THREAD_BARRIER) != 0) {
    return Fuseable.NONE;
    }
    else {
    m = s.requestFusion(requestedMode);
    }
    sourceMode = m;
    return m;
    }

    View Slide

  107. null comme signal de fusion
    poll comme mécanisme

    View Slide

  108. @Override
    public void onNext(T t) {
    if (sourceMode == ASYNC) {
    actual.onNext(null);
    }
    else {
    if (done) {
    return;
    }
    R v;
    try {
    v = Objects.requireNonNull(mapper.apply(t),
    "The mapper returned a null value.");
    }
    catch (Throwable e) {
    onError(e);
    return;

    View Slide

  109. @Override
    public void onNext(T t) {
    if (sourceMode == ASYNC) {
    actual.onNext(null);
    }
    else {
    if (done) {
    return;
    }
    R v;
    try {
    v = Objects.requireNonNull(mapper.apply(t),
    "The mapper returned a null value.");
    }
    catch (Throwable e) {
    onError(e);
    return;

    View Slide

  110. @Override
    @Nullable
    public R poll() {
    T v = s.poll();
    if (v != null) {
    //can throw exceptions
    return Objects.requireNonNull(mapper.apply(v));
    }
    return null;
    }

    View Slide

  111. @Override
    @Nullable
    public R poll() {
    T v = s.poll();
    if (v != null) {
    //can throw exceptions
    return Objects.requireNonNull(mapper.apply(v));
    }
    return null;
    }

    View Slide

  112. @Override
    public boolean isEmpty() {
    return s.isEmpty();
    }
    @Override
    public void clear() {
    s.clear();
    }
    @Override
    public int size() {
    return s.size();
    }

    View Slide

  113. Deuxième descente:
    concatMap

    View Slide

  114. l’opérateur
    concatMap
    que fait-il ?
    opérateurs 1-to-N
    opérateur interne et Subscriber
    interne

    View Slide

  115. que fait l’opérateur?

    View Slide

  116. Flux concatMap(
    Function> f)

    View Slide

  117. Flux concatMap(
    Function> f)

    View Slide

  118. Flux concatMap(
    Function> f)

    View Slide

  119. ça ressemble à une fonction qui
    pourrait être passée à map

    View Slide

  120. essayons...

    View Slide

  121. T
    map( )

    View Slide

  122. T
    map( )

    View Slide

  123. T
    map( )

    View Slide

  124. T
    map( )
    ceux-ci sont “inertes”
    Flux>

    View Slide

  125. map n’as aucune
    connaissance particulière sur
    le type

    View Slide

  126. ici est en fait Publisher
    seulement map ne sait pas qu’il
    doit y souscrire...

    View Slide

  127. T
    concatMap( )

    View Slide

  128. concatMap( )

    View Slide

  129. concatMap( )

    View Slide

  130. concatMap( )
    sous-flux souscrits et propagés
    Flux

    View Slide

  131. concatMap sait quoi faire
    d’un Publisher
    il y souscrit, et propage ses
    éléments

    View Slide

  132. concatMap concatène:
    pas de souscription au
    sous-Publisher suivant tant que le
    précédent n’as pas terminé

    View Slide

  133. opérateurs 1-to-N
    concatMap vs flatMap

    View Slide

  134. concatMap( )

    View Slide

  135. flatMap( )

    View Slide

  136. à quoi ressemble le code?

    View Slide

  137. opérateur de base
    Publisher classique

    View Slide

  138. final class FluxConcatMap extends FluxOperator {
    final Function super T, ? extends Publisher extends R>> mapper;
    final Supplier extends Queue> queueSupplier;
    final int prefetch;
    FluxConcatMap(Flux extends T> source,
    Function super T, ? extends Publisher extends R>> mapper,
    Supplier extends Queue> queueSupplier,
    int prefetch) {
    super(source);
    if (prefetch <= 0)
    throw new IllegalArgumentException("prefetch <= 0");
    this.mapper = Objects.requireNonNull(mapper, "mapper");
    this.queueSupplier = Objects.requireNonNull(queueSupplier);
    this.prefetch = prefetch;
    }
    @Override
    public void subscribe(CoreSubscriber super R> actual) {
    source.subscribe(new ConcatMapImmediate<>(s, mapper,
    queueSupplier, prefetch));
    }

    View Slide

  139. final class FluxConcatMap extends FluxOperator {
    final Function super T, ? extends Publisher extends R>> mapper;
    final Supplier extends Queue> queueSupplier;
    final int prefetch;
    FluxConcatMap(Flux extends T> source,
    Function super T, ? extends Publisher extends R>> mapper,
    Supplier extends Queue> queueSupplier,
    int prefetch) {
    super(source);
    if (prefetch <= 0)
    throw new IllegalArgumentException("prefetch <= 0");
    this.mapper = Objects.requireNonNull(mapper, "mapper");
    this.queueSupplier = Objects.requireNonNull(queueSupplier);
    this.prefetch = prefetch;
    }
    @Override
    public void subscribe(CoreSubscriber super R> actual) {
    source.subscribe(new ConcatMapImmediate<>(s, mapper,
    queueSupplier, prefetch));
    }

    View Slide

  140. ConcatMapImmediate
    Subscriber principal
    avec un peu plus d’état, coordonne les sous-Publisher

    View Slide

  141. static final class ConcatMapImmediate {
    final CoreSubscriber super R> actual;
    final Function super T, ? extends Publisher extends R>> mapper;
    final Supplier extends Queue> queueSupplier;
    final int prefetch;
    final int limit;
    Subscription s;
    int consumed;
    volatile Queue queue;
    volatile boolean done;
    volatile boolean cancelled;
    volatile boolean active;
    volatile Throwable error;
    volatile int wip;
    volatile int guard;
    int sourceMode;

    View Slide

  142. static final class ConcatMapImmediate {
    final CoreSubscriber super R> actual;
    final Function super T, ? extends Publisher extends R>> mapper;
    final Supplier extends Queue> queueSupplier;
    final int prefetch;
    final int limit;
    Subscription s;
    int consumed;
    volatile Queue queue;
    volatile boolean done;
    volatile boolean cancelled;
    volatile boolean active;
    volatile Throwable error;
    volatile int wip;
    volatile int guard;
    int sourceMode;

    View Slide

  143. static final AtomicReferenceFieldUpdater
    ERROR = AtomicReferenceFieldUpdater.newUpdater(ConcatMapImmediate.class,
    Throwable.class, "error");
    static final AtomicIntegerFieldUpdater
    WIP = AtomicIntegerFieldUpdater.newUpdater(ConcatMapImmediate.class,"wip");
    static final AtomicIntegerFieldUpdater
    GUARD = AtomicIntegerFieldUpdater.newUpdater(ConcatMapImmediate.class,
    "guard");
    final ConcatMapInner inner;

    View Slide

  144. @Override
    public void onSubscribe(Subscription s) {
    if (Operators.validate(this.s, s)) {
    this.s = s;
    if (s instanceof Fuseable.QueueSubscription) {
    if (m == Fuseable.SYNC) { ... }
    else if (m == Fuseable.ASYNC) { ... }
    else {
    queue = queueSupplier.get();
    }
    } else {
    queue = queueSupplier.get();
    }
    actual.onSubscribe(this);
    s.request(Operators.unboundedOrPrefetch(prefetch));
    }
    }

    View Slide

  145. ...
    if (s instanceof Fuseable.QueueSubscription) {
    Fuseable.QueueSubscription f = (Fuseable.QueueSubscription) s;
    int m = f.requestFusion(Fuseable.ANY);
    if (m == Fuseable.SYNC) {
    sourceMode = Fuseable.SYNC;
    queue = f;
    done = true;
    actual.onSubscribe(this);
    drain();
    return;
    }
    else if (m == Fuseable.ASYNC) {
    sourceMode = Fuseable.ASYNC;
    queue = f;
    } else {
    queue = queueSupplier.get();
    }
    }
    ...

    View Slide

  146. ...
    if (s instanceof Fuseable.QueueSubscription) {
    Fuseable.QueueSubscription f = (Fuseable.QueueSubscription) s;
    int m = f.requestFusion(Fuseable.ANY);
    if (m == Fuseable.SYNC) {
    sourceMode = Fuseable.SYNC;
    queue = f;
    done = true;
    actual.onSubscribe(this);
    drain();
    return;
    }
    else if (m == Fuseable.ASYNC) {
    sourceMode = Fuseable.ASYNC;
    queue = f;
    } else {
    queue = queueSupplier.get();
    }
    }
    ...
    drain( )?
    on va y revenir...

    View Slide

  147. @Override
    public void onComplete() {
    done = true;
    drain();
    }
    @Override
    public void request(long n) {
    inner.request(n);
    }
    @Override
    public void cancel() {
    if (!cancelled) {
    cancelled = true;
    inner.cancel();
    s.cancel();
    }
    }

    View Slide

  148. @Override
    public void onError(Throwable t) {
    if (Exceptions.addThrowable(ERROR, this, t)) {
    inner.cancel();
    if (GUARD.getAndIncrement(this) == 0) {
    t = Exceptions.terminate(ERROR, this);
    if (t != TERMINATED) {
    actual.onError(t);
    }
    }
    }
    else {
    Operators.onErrorDropped(t, actual.currentContext());
    }
    }

    View Slide

  149. @Override
    public void onError(Throwable t) {
    if (Exceptions.addThrowable(ERROR, this, t)) {
    inner.cancel();
    if (GUARD.getAndIncrement(this) == 0) {
    t = Exceptions.terminate(ERROR, this);
    if (t != TERMINATED) {
    actual.onError(t);
    }
    }
    }
    else {
    Operators.onErrorDropped(t, actual.currentContext());
    }
    }

    View Slide

  150. @Override
    public void onNext(T t) {
    if (sourceMode == Fuseable.ASYNC) {
    drain();
    }
    else if (!queue.offer(t)) {
    onError(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL);
    }
    else {
    drain();
    }
    }

    View Slide

  151. @Override
    public void onNext(T t) {
    if (sourceMode == Fuseable.ASYNC) {
    drain();
    }
    else if (!queue.offer(t)) {
    onError(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL);
    }
    else {
    drain();
    }
    }
    on va y revenir je
    vous dis...

    View Slide

  152. pour l’instant, précisons juste que dans le drain( )
    on va attacher
    le inner au Publisher créé par la fonction

    View Slide

  153. @Override
    public void onNext(T t) {
    if (sourceMode == Fuseable.ASYNC) {
    drain();
    }
    else if (!queue.offer(t)) {
    onError(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL);
    }
    else {
    drain();
    }
    }

    View Slide

  154. le inner appelle par la suite son parent
    via l’API suivante

    View Slide

  155. @Override
    public void innerNext(R value) {
    if (guard == 0 && GUARD.compareAndSet(this, 0, 1)) {
    actual.onNext(value);
    if (GUARD.compareAndSet(this, 1, 0)) {
    return;
    }
    Throwable e = Exceptions.terminate(ERROR, this);
    if (e != TERMINATED) {
    actual.onError(e);
    }
    }
    }

    View Slide

  156. @Override
    public void innerComplete() {
    active = false;
    drain();
    }

    View Slide

  157. @Override
    public void innerError(Throwable e) {
    if (Exceptions.addThrowable(ERROR, this, e)) {
    s.cancel();
    if (GUARD.getAndIncrement(this) == 0) {
    e = Exceptions.terminate(ERROR, this);
    if (e != TERMINATED) {
    actual.onError(e);
    }
    }
    }
    else {
    Operators.onErrorDropped(e, actual.currentContext());
    }
    }

    View Slide

  158. ConcatMapInner
    récupère les signaux des sous-Publisher
    s’assure que la backpressure est respectée

    View Slide

  159. static final class ConcatMapInner
    extends Operators.MultiSubscriptionSubscriber {
    final FluxConcatMapImmediate, R> parent;
    long produced;
    ConcatMapInner(FluxConcatMapSupport, R> parent) {
    super(Operators.emptySubscriber());
    this.parent = parent;
    }
    @Override
    public void onNext(R t) {
    produced++;
    parent.innerNext(t);
    }
    ...

    View Slide

  160. static final class ConcatMapInner
    extends Operators.MultiSubscriptionSubscriber {
    final FluxConcatMapImmediate, R> parent;
    long produced;
    ConcatMapInner(FluxConcatMapSupport, R> parent) {
    super(Operators.emptySubscriber());
    this.parent = parent;
    }
    @Override
    public void onNext(R t) {
    produced++;
    parent.innerNext(t);
    }
    ...

    View Slide

  161. static final class ConcatMapInner
    extends Operators.MultiSubscriptionSubscriber {
    ...
    @Override
    public void onError(Throwable t) {
    long p = produced;
    if (p != 0L) {
    produced = 0L;
    produced(p);
    }
    parent.innerError(t);
    }
    ...
    }

    View Slide

  162. static final class ConcatMapInner
    extends Operators.MultiSubscriptionSubscriber {
    ...
    @Override
    public void onComplete() {
    long p = produced;
    if (p != 0L) {
    produced = 0L;
    produced(p);
    }
    parent.innerComplete();
    }
    }
    }

    View Slide

  163. au plus profond:
    la drain loop

    View Slide

  164. Au plus profond: la
    `drain loop`
    gérer les race conditions
    work stealing

    View Slide

  165. void drain() {
    if (WIP.getAndIncrement(this) == 0) {
    for (; ; ) {
    if (cancelled) return;
    if (!active) {
    ...
    }
    if (WIP.decrementAndGet(this) == 0) {
    break;
    }
    }
    }
    }
    }

    View Slide

  166. void drain() {
    if (WIP.getAndIncrement(this) == 0) {
    for (; ; ) {
    if (cancelled) return;
    if (!active) {
    ...
    }
    if (WIP.decrementAndGet(this) == 0) {
    break;
    }
    }
    }
    }
    }

    View Slide

  167. if (cancelled) return;
    if (!active) {
    boolean d = done;
    T v;
    try {
    v = queue.poll();
    }
    catch (Throwable e) {
    actual.onError(e);
    return;
    }
    boolean empty = v == null;
    if (d && empty) {
    actual.onComplete();
    return;
    }
    ...

    View Slide

  168. ...
    if (!empty) {
    Publisher extends R> p;
    try {
    p = Objects.requireNonNull(mapper.apply(v));
    }
    catch (Throwable e) {
    actual.onError(e);
    return;
    }
    if (sourceMode != Fuseable.SYNC) {
    ...
    }
    active = true;
    p.subscribe(inner);
    }
    }
    if (WIP.decrementAndGet(this) == 0) {
    break;
    }

    View Slide

  169. ...
    if (sourceMode != Fuseable.SYNC) {
    int c = consumed + 1;
    if (c == limit) {
    consumed = 0;
    s.request(c);
    }
    else {
    consumed = c;
    }
    }
    active = true;
    p.subscribe(inner);
    }
    }
    if (WIP.decrementAndGet(this) == 0) {
    break;
    }

    View Slide

  170. work stealing

    View Slide

  171. void drain() {
    if (WIP.getAndIncrement(this) == 0) {
    for (; ; ) {
    if (cancelled) return;
    if (!active) {
    ...
    }
    if (WIP.decrementAndGet(this) == 0) {
    break;
    }
    }
    }
    }
    }

    View Slide

  172. thread 1 thread 2 thread 3
    sans work stealing
    onNext(3)
    empile 3
    1, 2, 3

    View Slide

  173. thread 1 thread 2 thread 3
    sans work stealing
    onNext(3)
    empile 3
    request( 1 )
    émet 1
    2, 3

    View Slide

  174. request( 2 )
    thread 1 thread 2 thread 3
    sans work stealing
    émet 2, 3
    onNext(3)
    empile 3
    request( 1 )
    émet 1

    View Slide

  175. thread 1 thread 2 thread 3
    sans work stealing
    onNext(3)
    empile 3 bloqué
    draine
    bloqué
    draine

    View Slide

  176. thread 1 thread 2 thread 3
    avec work stealing
    onNext(3)
    empile 3
    1, 2, 3

    View Slide

  177. thread 1 thread 2 thread 3
    avec work stealing
    onNext(3)
    empile 3
    request( 1 )
    émet 1
    2, 3

    View Slide

  178. request( 2 )
    thread 1 thread 2 thread 3
    avec work stealing
    onNext(3)
    empile 3
    request( 1 )
    émet 1
    émet 2, 3

    View Slide

  179. request( 2 )
    thread 1 thread 2 thread 3
    avec work stealing
    onNext(3)
    empile 3
    request( 1 )
    émet 1
    émet 2, 3 met juste la
    requête à jour et
    quitte la boucle
    WIP
    cette drainLoop a
    “gagné”

    View Slide

  180. Conclusion

    View Slide

  181. Questions?

    View Slide

  182. Merci !

    View Slide