From: Robert Varga Date: Sun, 18 Sep 2016 22:21:23 +0000 (+0200) Subject: Encapsulate ShardedDOMDataTreeProducer layout X-Git-Tag: release/boron-sr1~13 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=mdsal.git;a=commitdiff_plain;h=c182e1d32720044c42a187725e5effc57313b31d Encapsulate ShardedDOMDataTreeProducer layout This patch encapsulates all state related to sharding layout into an inner class, so that this information can be accessed concurrently. Change-Id: If66a7ac5b574d612b31e7c52156b80580ef9f79a Signed-off-by: Robert Varga (cherry picked from commit 09111573277ec258069ecba5e02ad8750ca50633) --- diff --git a/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ProducerLayout.java b/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ProducerLayout.java new file mode 100644 index 0000000000..fccf22ddb8 --- /dev/null +++ b/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ProducerLayout.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.dom.broker; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableBiMap.Builder; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier; +import org.opendaylight.mdsal.dom.api.DOMDataTreeProducer; +import org.opendaylight.mdsal.dom.api.DOMDataTreeShard; +import org.opendaylight.mdsal.dom.store.inmemory.DOMDataTreeShardProducer; +import org.opendaylight.mdsal.dom.store.inmemory.DOMDataTreeShardWriteTransaction; +import org.opendaylight.mdsal.dom.store.inmemory.WriteableDOMDataTreeShard; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class ProducerLayout { + private static final Logger LOG = LoggerFactory.getLogger(ProducerLayout.class); + + private final BiMap idToProducer; + private final Map children; + private final Map shardMap; + + private ProducerLayout(final Map shardMap, + final BiMap idToProducer, + final Map children) { + this.shardMap = ImmutableMap.copyOf(shardMap); + this.idToProducer = Preconditions.checkNotNull(idToProducer); + this.children = Preconditions.checkNotNull(children); + } + + static ProducerLayout create(final Map shardMap) { + return new ProducerLayout(shardMap, mapIdsToProducer(shardMap), ImmutableMap.of()); + } + + private static BiMap mapIdsToProducer( + final Map shardMap) { + final Multimap shardToId = ArrayListMultimap.create(); + // map which identifier belongs to which shard + for (final Entry entry : shardMap.entrySet()) { + shardToId.put(entry.getValue(), entry.getKey()); + } + + final Builder idToProducerBuilder = ImmutableBiMap.builder(); + for (final Entry> entry : shardToId.asMap().entrySet()) { + if (entry.getKey() instanceof WriteableDOMDataTreeShard) { + //create a single producer for all prefixes in a single shard + final DOMDataTreeShardProducer producer = ((WriteableDOMDataTreeShard) entry.getKey()) + .createProducer(entry.getValue()); + // id mapped to producers + for (final DOMDataTreeIdentifier id : entry.getValue()) { + idToProducerBuilder.put(id, producer); + } + } else { + LOG.error("Unable to create a producer for shard that's not a WriteableDOMDataTreeShard"); + } + } + + return idToProducerBuilder.build(); + } + + ProducerLayout addChild(final DOMDataTreeProducer producer, final Collection subtrees) { + final ImmutableMap.Builder cb = ImmutableMap.builder(); + cb.putAll(children); + for (final DOMDataTreeIdentifier s : subtrees) { + cb.put(s, producer); + } + + return new ProducerLayout(shardMap, idToProducer, cb.build()); + } + + ProducerLayout reshard(final Map shardMap) { + return new ProducerLayout(shardMap, mapIdsToProducer(shardMap), children); + } + + boolean haveSubtree(final DOMDataTreeIdentifier subtree) { + for (final DOMDataTreeIdentifier i : shardMap.keySet()) { + if (i.contains(subtree)) { + return true; + } + } + + return false; + } + + DOMDataTreeProducer lookupChild(final DOMDataTreeIdentifier path) { + for (final Entry e : children.entrySet()) { + if (e.getKey().contains(path)) { + // FIXME: does this match wildcards? + return e.getValue(); + } + } + + return null; + } + + Set getChildTrees() { + return children.keySet(); + } + + void checkAvailable(final Collection base, final PathArgument child) { + if (!children.isEmpty()) { + final Collection args = new ArrayList<>(base.size() + 1); + args.addAll(base); + args.add(child); + + final YangInstanceIdentifier path = YangInstanceIdentifier.create(args); + for (final DOMDataTreeIdentifier c : children.keySet()) { + Preconditions.checkArgument(!c.getRootIdentifier().contains(path), + "Path {%s} is not available to this cursor since it's already claimed by a child producer", path); + } + } + } + + Map createTransactions() { + return Maps.transformValues(idToProducer, DOMDataTreeShardProducer::createTransaction); + } +} diff --git a/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTree.java b/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTree.java index 6f30d207f8..4ef05c9e40 100644 --- a/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTree.java +++ b/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTree.java @@ -183,7 +183,7 @@ public final class ShardedDOMDataTree implements DOMDataTreeService, DOMDataTree final ShardedDOMDataTreeProducer castedProducer = ((ShardedDOMDataTreeProducer) producer); simpleLoopCheck(subtrees, castedProducer.getSubtrees()); // FIXME: We should also unbound listeners - castedProducer.boundToListener(listenerContext); + castedProducer.bindToListener(listenerContext); } for (final DOMDataTreeIdentifier subtree : subtrees) { diff --git a/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTreeProducer.java b/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTreeProducer.java index 022215a6b2..b7c2e58e52 100644 --- a/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTreeProducer.java +++ b/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTreeProducer.java @@ -9,17 +9,9 @@ package org.opendaylight.mdsal.dom.broker; import com.google.common.base.Preconditions; import com.google.common.base.Verify; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.BiMap; -import com.google.common.collect.Collections2; -import com.google.common.collect.ImmutableBiMap; -import com.google.common.collect.ImmutableBiMap.Builder; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Multimap; import java.util.Collection; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; @@ -30,9 +22,6 @@ import org.opendaylight.mdsal.dom.api.DOMDataTreeProducer; import org.opendaylight.mdsal.dom.api.DOMDataTreeProducerBusyException; import org.opendaylight.mdsal.dom.api.DOMDataTreeProducerException; import org.opendaylight.mdsal.dom.api.DOMDataTreeShard; -import org.opendaylight.mdsal.dom.store.inmemory.DOMDataTreeShardProducer; -import org.opendaylight.mdsal.dom.store.inmemory.WriteableDOMDataTreeShard; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,9 +31,6 @@ class ShardedDOMDataTreeProducer implements DOMDataTreeProducer { private final Set subtrees; private final ShardedDOMDataTree dataTree; - private BiMap idToProducer = ImmutableBiMap.of(); - private Map idToShard; - private static final AtomicReferenceFieldUpdater CURRENT_UPDATER = AtomicReferenceFieldUpdater.newUpdater(ShardedDOMDataTreeProducer.class, ShardedDOMDataTreeWriteTransaction.class, "currentTx"); @@ -64,56 +50,21 @@ class ShardedDOMDataTreeProducer implements DOMDataTreeProducer { AtomicIntegerFieldUpdater.newUpdater(ShardedDOMDataTreeProducer.class, "closed"); private volatile int closed; - @GuardedBy("this") - private Map children = ImmutableMap.of(); - @GuardedBy("this") - private Set childRoots = ImmutableSet.of(); + private volatile ShardedDOMDataTreeListenerContext attachedListener; + private volatile ProducerLayout layout; - @GuardedBy("this") - private ShardedDOMDataTreeListenerContext attachedListener; - - ShardedDOMDataTreeProducer(final ShardedDOMDataTree dataTree, + private ShardedDOMDataTreeProducer(final ShardedDOMDataTree dataTree, final Collection subtrees, - final Map shardMap, - final Multimap shardToId) { + final Map shardMap) { this.dataTree = Preconditions.checkNotNull(dataTree); - if (!shardToId.isEmpty()) { - this.idToProducer = mapIdsToProducer(shardToId); - } - idToShard = ImmutableMap.copyOf(shardMap); this.subtrees = ImmutableSet.copyOf(subtrees); + this.layout = ProducerLayout.create(shardMap); } static DOMDataTreeProducer create(final ShardedDOMDataTree dataTree, final Collection subtrees, final Map shardMap) { - final Multimap shardToIdentifiers = ArrayListMultimap.create(); - // map which identifier belongs to which shard - for (final Entry entry : shardMap.entrySet()) { - shardToIdentifiers.put(entry.getValue(), entry.getKey()); - } - - return new ShardedDOMDataTreeProducer(dataTree, subtrees, shardMap, shardToIdentifiers); - } - - private static BiMap mapIdsToProducer( - final Multimap shardToId) { - final Builder idToProducerBuilder = ImmutableBiMap.builder(); - for (final Entry> entry : shardToId.asMap().entrySet()) { - if (entry.getKey() instanceof WriteableDOMDataTreeShard) { - //create a single producer for all prefixes in a single shard - final DOMDataTreeShardProducer producer = ((WriteableDOMDataTreeShard) entry.getKey()) - .createProducer(entry.getValue()); - // id mapped to producers - for (final DOMDataTreeIdentifier id : entry.getValue()) { - idToProducerBuilder.put(id, producer); - } - } else { - LOG.error("Unable to create a producer for shard that's not a WriteableDOMDataTreeShard"); - } - } - - return idToProducerBuilder.build(); + return new ShardedDOMDataTreeProducer(dataTree, subtrees, shardMap); } private void checkNotClosed() { @@ -127,13 +78,7 @@ class ShardedDOMDataTreeProducer implements DOMDataTreeProducer { void subshardAdded(final Map shardMap) { checkIdle(); - final Multimap shardToIdentifiers = ArrayListMultimap.create(); - // map which identifier belongs to which shard - for (final Entry entry : shardMap.entrySet()) { - shardToIdentifiers.put(entry.getValue(), entry.getKey()); - } - this.idToProducer = mapIdsToProducer(shardToIdentifiers); - idToShard = ImmutableMap.copyOf(shardMap); + layout = layout.reshard(shardMap); } @Override @@ -141,29 +86,14 @@ class ShardedDOMDataTreeProducer implements DOMDataTreeProducer { checkNotClosed(); checkIdle(); - final ShardedDOMDataTreeWriteTransaction ret; LOG.debug("Creating transaction from producer {}", this); final ShardedDOMDataTreeWriteTransaction current = CURRENT_UPDATER.getAndSet(this, null); + final ShardedDOMDataTreeWriteTransaction ret; if (isolated) { - // Isolated case. If we have a previous transaction, submit it before returning this one. - synchronized (this) { - if (current != null) { - submitTransaction(current); - } - ret = new ShardedDOMDataTreeWriteTransaction(this, idToProducer, childRoots); - } + ret = createIsolatedTransaction(layout, current); } else { - // Non-isolated case, see if we can reuse the transaction - if (current != null) { - LOG.debug("Reusing previous transaction {} since there is still a transaction inflight", - current.getIdentifier()); - ret = current; - } else { - synchronized (this) { - ret = new ShardedDOMDataTreeWriteTransaction(this, idToProducer, childRoots); - } - } + ret = createReusedTransaction(layout, current); } final boolean success = OPEN_UPDATER.compareAndSet(this, null, ret); @@ -171,32 +101,51 @@ class ShardedDOMDataTreeProducer implements DOMDataTreeProducer { return ret; } + // This may look weird, but this has side-effects on local's producers, hence it needs to be properly synchronized + // so that it happens-after submitTransaction() which may have been stolen by a callback. @GuardedBy("this") - private void submitTransaction(final ShardedDOMDataTreeWriteTransaction current) { - lastTx = current; - current.doSubmit(this::transactionSuccessful, this::transactionFailed); + private ShardedDOMDataTreeWriteTransaction createTransaction(final ProducerLayout local) { + return new ShardedDOMDataTreeWriteTransaction(this, local.createTransactions(), local); + } - @GuardedBy("this") - private boolean haveSubtree(final DOMDataTreeIdentifier subtree) { - for (final DOMDataTreeIdentifier i : idToShard.keySet()) { - if (i.contains(subtree)) { - return true; - } + // Isolated case. If we have a previous transaction, submit it before returning this one. + private synchronized ShardedDOMDataTreeWriteTransaction createIsolatedTransaction( + final ProducerLayout local, final ShardedDOMDataTreeWriteTransaction current) { + if (current != null) { + submitTransaction(current); } - return false; + return createTransaction(local); } - @GuardedBy("this") - private DOMDataTreeProducer lookupChild(final DOMDataTreeIdentifier domDataTreeIdentifier) { - for (final Entry e : children.entrySet()) { - if (e.getKey().contains(domDataTreeIdentifier)) { - return e.getValue(); + private ShardedDOMDataTreeWriteTransaction createReusedTransaction(final ProducerLayout local, + final ShardedDOMDataTreeWriteTransaction current) { + if (current != null) { + // Lock-free fast path + if (local.equals(current.getLayout())) { + LOG.debug("Reusing previous transaction {} since there is still a transaction inflight", + current.getIdentifier()); + return current; } + + synchronized (this) { + submitTransaction(current); + return createTransaction(local); + } + } + + // Null indicates we have not seen a previous transaction -- which does not mean it is ready, as it may have + // been stolen and in is process of being submitted. + synchronized (this) { + return createTransaction(local); } + } - return null; + @GuardedBy("this") + private void submitTransaction(final ShardedDOMDataTreeWriteTransaction tx) { + lastTx = tx; + tx.doSubmit(this::transactionSuccessful, this::transactionFailed); } @Override @@ -204,45 +153,36 @@ class ShardedDOMDataTreeProducer implements DOMDataTreeProducer { checkNotClosed(); checkIdle(); + final ProducerLayout local = layout; + for (final DOMDataTreeIdentifier s : subtrees) { // Check if the subtree was visible at any time - Preconditions.checkArgument(haveSubtree(s), "Subtree %s was never available in producer %s", s, this); + Preconditions.checkArgument(local.haveSubtree(s), "Subtree %s was never available in producer %s", s, this); // Check if the subtree has not been delegated to a child - final DOMDataTreeProducer child = lookupChild(s); + final DOMDataTreeProducer child = local.lookupChild(s); Preconditions.checkArgument(child == null, "Subtree %s is delegated to child producer %s", s, child); // Check if part of the requested subtree is not delegated to a child. - for (final DOMDataTreeIdentifier c : children.keySet()) { + for (final DOMDataTreeIdentifier c : local.getChildTrees()) { Preconditions.checkArgument(!s.contains(c), "Subtree %s cannot be delegated as it is a superset of already-delegated %s", s, c); } } - synchronized (this) { - final DOMDataTreeProducer ret = dataTree.createProducer(this, subtrees); - final ImmutableMap.Builder cb = ImmutableMap.builder(); - cb.putAll(children); - for (final DOMDataTreeIdentifier s : subtrees) { - cb.put(s, ret); - } - children = cb.build(); - childRoots = ImmutableSet.copyOf(Collections2.transform(children.keySet(), - DOMDataTreeIdentifier::getRootIdentifier)); - return ret; + final DOMDataTreeProducer ret; + synchronized (this) { + ret = dataTree.createProducer(this, subtrees); } + + layout = local.addChild(ret, subtrees); + return ret; } boolean isDelegatedToChild(final DOMDataTreeIdentifier path) { - for (final DOMDataTreeIdentifier c : children.keySet()) { - if (c.contains(path)) { - return true; - } - } - return false; + return layout.lookupChild(path) != null; } - @Override public void close() throws DOMDataTreeProducerException { if (openTx != null) { @@ -317,6 +257,7 @@ class ShardedDOMDataTreeProducer implements DOMDataTreeProducer { LOG.debug("Transaction {} failed", tx.getIdentifier(), throwable); tx.onTransactionFailure(throwable); + // FIXME: transaction failure should result in a hard error transactionCompleted(tx); } @@ -327,10 +268,15 @@ class ShardedDOMDataTreeProducer implements DOMDataTreeProducer { } } - synchronized void boundToListener(final ShardedDOMDataTreeListenerContext listener) { - // FIXME: Add option to detach - Preconditions.checkState(this.attachedListener == null, "Producer %s is already attached to other listener.", - listener.getListener()); + void bindToListener(final ShardedDOMDataTreeListenerContext listener) { + Preconditions.checkNotNull(listener); + + final ShardedDOMDataTreeListenerContext local = attachedListener; + if (local != null) { + throw new IllegalStateException(String.format("Producer %s is already attached to listener %s", this, + local.getListener())); + } + this.attachedListener = listener; } } diff --git a/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTreeWriteTransaction.java b/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTreeWriteTransaction.java index fe938d8768..53feef2dc4 100644 --- a/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTreeWriteTransaction.java +++ b/dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTreeWriteTransaction.java @@ -9,19 +9,16 @@ package org.opendaylight.mdsal.dom.broker; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; import com.google.common.util.concurrent.CheckedFuture; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; -import java.util.Collection; +import java.util.ArrayDeque; import java.util.Deque; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -33,9 +30,7 @@ import org.opendaylight.mdsal.common.api.TransactionCommitFailedException; import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction; import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier; import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteCursor; -import org.opendaylight.mdsal.dom.store.inmemory.DOMDataTreeShardProducer; import org.opendaylight.mdsal.dom.store.inmemory.DOMDataTreeShardWriteTransaction; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.slf4j.Logger; @@ -48,9 +43,9 @@ final class ShardedDOMDataTreeWriteTransaction implements DOMDataTreeCursorAware TransactionCommitFailedExceptionMapper.create("submit"); private static final AtomicLong COUNTER = new AtomicLong(); - private final Map idToTransaction; - private final Collection childBoundaries; + private final Map transactions; private final ShardedDOMDataTreeProducer producer; + private final ProducerLayout layout; private final String identifier; private final SettableFuture future = SettableFuture.create(); @@ -64,18 +59,16 @@ final class ShardedDOMDataTreeWriteTransaction implements DOMDataTreeCursorAware private DOMDataTreeWriteCursor openCursor; ShardedDOMDataTreeWriteTransaction(final ShardedDOMDataTreeProducer producer, - final Map idToProducer, - final Set childRoots) { + final Map transactions, final ProducerLayout layout) { this.producer = Preconditions.checkNotNull(producer); + this.transactions = ImmutableMap.copyOf(transactions); + this.layout = Preconditions.checkNotNull(layout); this.identifier = "SHARDED-DOM-" + COUNTER.getAndIncrement(); - idToTransaction = ImmutableMap.copyOf(Maps.transformValues(idToProducer, - DOMDataTreeShardProducer::createTransaction)); - childBoundaries = Preconditions.checkNotNull(childRoots); LOG.debug("Created new transaction {}", identifier); } private DOMDataTreeShardWriteTransaction lookup(final DOMDataTreeIdentifier prefix) { - for (final Entry e : idToTransaction.entrySet()) { + for (final Entry e : transactions.entrySet()) { if (e.getKey().contains(prefix)) { Preconditions.checkArgument(!producer.isDelegatedToChild(prefix), "Path %s is delegated to child producer.", prefix); @@ -101,7 +94,7 @@ final class ShardedDOMDataTreeWriteTransaction implements DOMDataTreeCursorAware if (openCursor != null) { openCursor.close(); } - for (final DOMDataTreeShardWriteTransaction tx : idToTransaction.values()) { + for (final DOMDataTreeShardWriteTransaction tx : transactions.values()) { tx.close(); } @@ -135,7 +128,7 @@ final class ShardedDOMDataTreeWriteTransaction implements DOMDataTreeCursorAware final BiConsumer failure) { final ListenableFuture> listListenableFuture = Futures.allAsList( - idToTransaction.values().stream().map(tx -> { + transactions.values().stream().map(tx -> { LOG.debug("Readying tx {}", identifier); tx.ready(); return tx.submit(); @@ -167,10 +160,9 @@ final class ShardedDOMDataTreeWriteTransaction implements DOMDataTreeCursorAware } private class DelegatingCursor implements DOMDataTreeWriteCursor { - + private final Deque path = new ArrayDeque<>(); private final DOMDataTreeWriteCursor delegate; private final DOMDataTreeIdentifier rootPosition; - private final Deque path = new LinkedList<>(); DelegatingCursor(final DOMDataTreeWriteCursor delegate, final DOMDataTreeIdentifier rootPosition) { this.delegate = Preconditions.checkNotNull(delegate); @@ -244,16 +236,11 @@ final class ShardedDOMDataTreeWriteTransaction implements DOMDataTreeCursorAware } void checkAvailable(final PathArgument child) { - path.add(child); - final YangInstanceIdentifier yid = YangInstanceIdentifier.create(path); - childBoundaries.forEach(id -> { - if (id.contains(yid)) { - path.removeLast(); - throw new IllegalArgumentException("Path {" + yid + "} is not available to this cursor" - + " since it's already claimed by a child producer"); - } - }); - path.removeLast(); + layout.checkAvailable(path, child); } } + + ProducerLayout getLayout() { + return layout; + } }