murblanc commented on a change in pull request #2291:
URL: https://github.com/apache/lucene-solr/pull/2291#discussion_r568912229



##########
File path: 
solr/core/src/java/org/apache/solr/cluster/events/impl/DelegatingClusterEventProducer.java
##########
@@ -90,7 +96,9 @@ public void setDelegate(ClusterEventProducer newDelegate) {
         log.debug("--- delegate {} already in state {}", delegate, 
delegate.getState());
       }
     }
-    this.version++;
+    if (versionTracker != null) {

Review comment:
       We have a synchronization issue (memory barrier type, not concurrent 
access type). The thread calling `setDelegate()` is accessing `versionTracker` 
set by another thread without synchronization.
   Can be fixed by making `versionTracker` volatile.

##########
File path: solr/core/src/test/org/apache/solr/cluster/VersionTrackerImpl.java
##########
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.cluster;
+
+import org.apache.solr.common.util.TimeSource;
+import org.apache.solr.util.TimeOut;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class VersionTrackerImpl implements VersionTracker {
+    private int version = 0;
+
+    @Override
+    public synchronized void increment() {
+        version++;
+        this.notifyAll();
+    }
+
+    @Override
+    public int waitForVersionChange(int currentVersion, int timeoutSec) throws 
InterruptedException, TimeoutException {
+        TimeOut timeout = new TimeOut(timeoutSec, TimeUnit.SECONDS, 
TimeSource.NANO_TIME);
+        int newVersion = currentVersion;
+        while (! timeout.hasTimedOut()) {
+            synchronized (this) {
+                if ((newVersion = version) != currentVersion) {
+                    break;
+                }
+                this.wait(timeout.timeLeft(TimeUnit.MILLISECONDS));
+            }
+        }
+        if (newVersion < currentVersion) {
+            // ArithmeticException? This means we overflowed
+            throw new RuntimeException("Invalid version - went back! 
currentVersion=" + currentVersion +
+                    " newVersion=" + newVersion);
+        } else if (newVersion == currentVersion) {
+            throw new TimeoutException("Timed out waiting for version 
change.");

Review comment:
       Add the version value to the exception, might help debug tests.

##########
File path: solr/core/src/java/org/apache/solr/cluster/VersionTracker.java
##########
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.cluster;
+
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Allows for tracking state change from test classes. Typical use will be to 
set a version tracker on a stateful
+ * object, which will call {@link #increment()} every time state changes. Test 
clients observing the state will call
+ * {@link #waitForVersionChange(int, int)} to be notified of the next 
increment call.
+ */
+public interface VersionTracker {

Review comment:
       Tracking versions by incrementing is one possible implementation of this 
interface, but maybe the interface doesn't have to hint that this is the 
implementation?
   Renaming `increment` into `notifyEvent` or something similar and 
`VersionTracker` into `NotificationCallback` would make it more generic (not 
suggesting these actual names, but you get the idea).
   
   `waitForVersionChange` doesn't have to be part of the interface. Test code 
can use the implementation it instantiates directly. Some tests might not even 
want to wait but rather do other things with the notification (like count how 
many notifications are received, or make sure none are received etc).

##########
File path: solr/core/src/test/org/apache/solr/cluster/VersionTrackerImpl.java
##########
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.cluster;
+
+import org.apache.solr.common.util.TimeSource;
+import org.apache.solr.util.TimeOut;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class VersionTrackerImpl implements VersionTracker {
+    private int version = 0;
+
+    @Override
+    public synchronized void increment() {
+        version++;
+        this.notifyAll();
+    }
+
+    @Override
+    public int waitForVersionChange(int currentVersion, int timeoutSec) throws 
InterruptedException, TimeoutException {
+        TimeOut timeout = new TimeOut(timeoutSec, TimeUnit.SECONDS, 
TimeSource.NANO_TIME);
+        int newVersion = currentVersion;
+        while (! timeout.hasTimedOut()) {

Review comment:
       formatting: remove space after `!`

##########
File path: 
solr/core/src/java/org/apache/solr/cluster/placement/impl/DelegatingPlacementPluginFactory.java
##########
@@ -39,18 +39,20 @@ public PlacementPlugin createPluginInstance() {
     }
   }
 
+  @VisibleForTesting
+  public void setVersionTracker(VersionTracker tracker) {
+    versionTracker = tracker;
+  }
+
   public void setDelegate(PlacementPluginFactory<? extends 
PlacementPluginConfig> delegate) {
     this.delegate = delegate;
-    this.version++;
+    if (versionTracker != null) {

Review comment:
       If we want to be strict about correctness, we should copy 
`versionTracker` into another variable then test and call, otherwise it can be 
non `null` when evaluating the if condition then `null` on the next line when 
dereferencing it.

##########
File path: solr/core/src/test/org/apache/solr/cluster/VersionTrackerImpl.java
##########
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.cluster;
+
+import org.apache.solr.common.util.TimeSource;
+import org.apache.solr.util.TimeOut;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class VersionTrackerImpl implements VersionTracker {
+    private int version = 0;
+
+    @Override
+    public synchronized void increment() {
+        version++;
+        this.notifyAll();
+    }
+
+    @Override
+    public int waitForVersionChange(int currentVersion, int timeoutSec) throws 
InterruptedException, TimeoutException {
+        TimeOut timeout = new TimeOut(timeoutSec, TimeUnit.SECONDS, 
TimeSource.NANO_TIME);
+        int newVersion = currentVersion;
+        while (! timeout.hasTimedOut()) {
+            synchronized (this) {
+                if ((newVersion = version) != currentVersion) {
+                    break;
+                }
+                this.wait(timeout.timeLeft(TimeUnit.MILLISECONDS));
+            }
+        }
+        if (newVersion < currentVersion) {
+            // ArithmeticException? This means we overflowed

Review comment:
       Likely bad call from the test...

##########
File path: solr/core/src/test/org/apache/solr/cluster/VersionTrackerImpl.java
##########
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.cluster;
+
+import org.apache.solr.common.util.TimeSource;
+import org.apache.solr.util.TimeOut;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class VersionTrackerImpl implements VersionTracker {
+    private int version = 0;
+
+    @Override
+    public synchronized void increment() {
+        version++;
+        this.notifyAll();
+    }
+
+    @Override
+    public int waitForVersionChange(int currentVersion, int timeoutSec) throws 
InterruptedException, TimeoutException {
+        TimeOut timeout = new TimeOut(timeoutSec, TimeUnit.SECONDS, 
TimeSource.NANO_TIME);
+        int newVersion = currentVersion;
+        while (! timeout.hasTimedOut()) {

Review comment:
       I also realize I would have put the while loop inside the synchronized 
block, and now I wonder which way is better... Re-acquiring it over and over 
again or not holding it while seeing if a timeout occurred.

##########
File path: 
solr/core/src/test/org/apache/solr/cluster/events/ClusterEventProducerTest.java
##########
@@ -102,7 +105,7 @@ public void teardown() throws Exception {
 
   @Test
   public void testEvents() throws Exception {
-    int version = waitForVersionChange(-1, 10);
+    int version = versionTracker.waitForVersionChange(-1, 10);

Review comment:
       Maybe we need a `getCurrentVersion` in `VersionTrackerImpl` so a given 
instance can be reused more easily in a given test?

##########
File path: 
solr/core/src/test/org/apache/solr/cluster/events/ClusterEventProducerTest.java
##########
@@ -102,7 +105,7 @@ public void teardown() throws Exception {
 
   @Test
   public void testEvents() throws Exception {
-    int version = waitForVersionChange(-1, 10);
+    int version = versionTracker.waitForVersionChange(-1, 10);

Review comment:
       Version starts at 0 in the implementation of the version tracker, so 
what does it mean to wait for it to be -1?

##########
File path: 
solr/core/src/test/org/apache/solr/cluster/CountingStateChangeListener.java
##########
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.cluster;
+
+import org.apache.solr.common.util.TimeSource;
+import org.apache.solr.util.TimeOut;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * A counting StateChangeListener that will internally track how many times 
{@link #stateChanged()} has been called.
+ * Consumers can compare the number of state change calls before and after an 
event to determine if they should proceed,
+ * made simple with {@link #waitForVersionChange(int, int)} method.
+ */
+public class CountingStateChangeListener implements StateChangeListener {
+    private int version = 0;
+
+    @Override
+    public synchronized void stateChanged() {
+        version++;
+        this.notifyAll();
+    }
+
+    /**
+     * Given a last known number of state changes, wait for additional changes 
to come in. If no state changes have
+     * occurred beyond the known value, this method will wait for additional 
changes to come in.
+     * If the current number of change events is unknown to the caller, then 
this method can be called with <tt>-1</tt>
+     * to return immediately with the number of events up to this point.
+     * @param lastVersion the previous number of changes seen
+     * @param timeoutSec how long to wait for additional changes to occur
+     * @return the number of changes seen since initialization
+     */
+    public int waitForVersionChange(int lastVersion, int timeoutSec) throws 
InterruptedException, TimeoutException {
+        TimeOut timeout = new TimeOut(timeoutSec, TimeUnit.SECONDS, 
TimeSource.NANO_TIME);
+        int newVersion = lastVersion;
+        synchronized (this) {
+            while (!timeout.hasTimedOut() && (newVersion = version) != 
lastVersion) {

Review comment:
       I don't get the condition here. Shouldn't we loop while `version == 
lastVersion` (so we exit the loop when it changes or when we time out) rather 
than loop while they're different?
   
   I suspect that the test without this improved lockstep synchronization was 
passing always on your machine and it continues to pass for the same reason.

##########
File path: 
solr/core/src/test/org/apache/solr/cluster/CountingStateChangeListener.java
##########
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.cluster;
+
+import org.apache.solr.common.util.TimeSource;
+import org.apache.solr.util.TimeOut;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * A counting StateChangeListener that will internally track how many times 
{@link #stateChanged()} has been called.
+ * Consumers can compare the number of state change calls before and after an 
event to determine if they should proceed,
+ * made simple with {@link #waitForVersionChange(int, int)} method.
+ */
+public class CountingStateChangeListener implements StateChangeListener {
+    private int version = 0;
+
+    @Override
+    public synchronized void stateChanged() {
+        version++;
+        this.notifyAll();
+    }
+
+    /**
+     * Given a last known number of state changes, wait for additional changes 
to come in. If no state changes have
+     * occurred beyond the known value, this method will wait for additional 
changes to come in.
+     * If the current number of change events is unknown to the caller, then 
this method can be called with <tt>-1</tt>
+     * to return immediately with the number of events up to this point.
+     * @param lastVersion the previous number of changes seen
+     * @param timeoutSec how long to wait for additional changes to occur
+     * @return the number of changes seen since initialization
+     */
+    public int waitForVersionChange(int lastVersion, int timeoutSec) throws 
InterruptedException, TimeoutException {
+        TimeOut timeout = new TimeOut(timeoutSec, TimeUnit.SECONDS, 
TimeSource.NANO_TIME);
+        int newVersion = lastVersion;

Review comment:
       This assignment is not needed. I assume the compiler is unhappy if it is 
just removed, but maybe doing the assignment to `newVersion` first before the 
timeout check in the while condition makes the compiler happy?




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscr...@lucene.apache.org
For additional commands, e-mail: issues-h...@lucene.apache.org

Reply via email to