Skip to content

Commit 07026d7

Browse files
committed
Fix queue capacity invariants
1 parent 8cb4067 commit 07026d7

14 files changed

Lines changed: 604 additions & 64 deletions

File tree

iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/agent/runtime/PipeConfigRegionListener.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ public synchronized void increaseReference(final PipeParameters parameters)
5050
public synchronized void decreaseReference(final PipeParameters parameters)
5151
throws IllegalPathException {
5252
if (!ConfigRegionListeningFilter.parseListeningPlanTypeSet(parameters).isEmpty()) {
53+
if (listeningQueueReferenceCount == 0) {
54+
return;
55+
}
5356
listeningQueueReferenceCount--;
5457
if (listeningQueueReferenceCount == 0) {
5558
listeningQueue.close();

iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/coordinator/task/PipeTaskCoordinatorLock.java

Lines changed: 66 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -24,74 +24,105 @@
2424
import org.slf4j.Logger;
2525
import org.slf4j.LoggerFactory;
2626

27-
import java.util.concurrent.Semaphore;
2827
import java.util.concurrent.TimeUnit;
2928

3029
/**
3130
* {@link PipeTaskCoordinatorLock} is a cross-thread lock for pipe task coordinator. It is used to
3231
* ensure that only one thread can execute the pipe task coordinator at the same time.
3332
*
34-
* <p>Uses {@link Semaphore} instead of {@link java.util.concurrent.locks.ReentrantLock} to support
35-
* cross-thread acquire/release, which is required by the procedure recovery mechanism: locks may be
36-
* acquired on the StateMachineUpdater thread during {@code restoreLock()} and released on a
37-
* ProcedureCoreWorker thread after execution.
33+
* <p>Supports cross-thread acquire/release, which is required by the procedure recovery mechanism:
34+
* locks may be acquired on the StateMachineUpdater thread during {@code restoreLock()} and released
35+
* on a ProcedureCoreWorker thread after execution.
3836
*/
3937
public class PipeTaskCoordinatorLock {
4038

4139
private static final Logger LOGGER = LoggerFactory.getLogger(PipeTaskCoordinatorLock.class);
4240

43-
private final Semaphore semaphore = new Semaphore(1);
41+
private boolean locked = false;
4442

4543
public void lock() {
4644
LOGGER.debug(
4745
ManagerMessages.PIPETASKCOORDINATOR_LOCK_WAITING_FOR_THREAD,
4846
Thread.currentThread().getName());
49-
try {
50-
semaphore.acquire();
51-
LOGGER.debug(
52-
ManagerMessages.PIPETASKCOORDINATOR_LOCK_ACQUIRED_BY_THREAD,
53-
Thread.currentThread().getName());
54-
} catch (final InterruptedException e) {
47+
48+
boolean interrupted = false;
49+
synchronized (this) {
50+
while (locked) {
51+
try {
52+
wait();
53+
} catch (final InterruptedException e) {
54+
interrupted = true;
55+
LOGGER.error(
56+
ManagerMessages.INTERRUPTED_WHILE_WAITING_FOR_PIPETASKCOORDINATOR_LOCK_CURRENT_THREAD,
57+
Thread.currentThread().getName());
58+
}
59+
}
60+
locked = true;
61+
}
62+
63+
if (interrupted) {
5564
Thread.currentThread().interrupt();
56-
LOGGER.error(
57-
ManagerMessages.INTERRUPTED_WHILE_WAITING_FOR_PIPETASKCOORDINATOR_LOCK_CURRENT_THREAD,
58-
Thread.currentThread().getName());
5965
}
66+
LOGGER.debug(
67+
ManagerMessages.PIPETASKCOORDINATOR_LOCK_ACQUIRED_BY_THREAD,
68+
Thread.currentThread().getName());
6069
}
6170

6271
public boolean tryLock() {
63-
try {
64-
LOGGER.debug(
65-
ManagerMessages.PIPETASKCOORDINATOR_LOCK_WAITING_FOR_THREAD,
66-
Thread.currentThread().getName());
67-
if (semaphore.tryAcquire(10, TimeUnit.SECONDS)) {
68-
LOGGER.debug(
69-
ManagerMessages.PIPETASKCOORDINATOR_LOCK_ACQUIRED_BY_THREAD,
70-
Thread.currentThread().getName());
71-
return true;
72-
} else {
73-
LOGGER.info(
74-
ManagerMessages.PIPETASKCOORDINATOR_LOCK_FAILED_TO_ACQUIRE_BY_THREAD_BECAUSE_OF_TIMEOUT,
75-
Thread.currentThread().getName());
76-
return false;
77-
}
78-
} catch (InterruptedException e) {
79-
Thread.currentThread().interrupt();
72+
LOGGER.debug(
73+
ManagerMessages.PIPETASKCOORDINATOR_LOCK_WAITING_FOR_THREAD,
74+
Thread.currentThread().getName());
75+
if (Thread.currentThread().isInterrupted()) {
8076
LOGGER.error(
8177
ManagerMessages.INTERRUPTED_WHILE_WAITING_FOR_PIPETASKCOORDINATOR_LOCK_CURRENT_THREAD,
8278
Thread.currentThread().getName());
8379
return false;
8480
}
81+
82+
final long deadlineNanos = System.nanoTime() + TimeUnit.SECONDS.toNanos(10);
83+
synchronized (this) {
84+
while (locked) {
85+
final long remainingNanos = deadlineNanos - System.nanoTime();
86+
if (remainingNanos <= 0) {
87+
LOGGER.info(
88+
ManagerMessages
89+
.PIPETASKCOORDINATOR_LOCK_FAILED_TO_ACQUIRE_BY_THREAD_BECAUSE_OF_TIMEOUT,
90+
Thread.currentThread().getName());
91+
return false;
92+
}
93+
try {
94+
TimeUnit.NANOSECONDS.timedWait(this, remainingNanos);
95+
} catch (InterruptedException e) {
96+
Thread.currentThread().interrupt();
97+
LOGGER.error(
98+
ManagerMessages.INTERRUPTED_WHILE_WAITING_FOR_PIPETASKCOORDINATOR_LOCK_CURRENT_THREAD,
99+
Thread.currentThread().getName());
100+
return false;
101+
}
102+
}
103+
locked = true;
104+
}
105+
106+
LOGGER.debug(
107+
ManagerMessages.PIPETASKCOORDINATOR_LOCK_ACQUIRED_BY_THREAD,
108+
Thread.currentThread().getName());
109+
return true;
85110
}
86111

87112
public void unlock() {
88-
semaphore.release();
113+
synchronized (this) {
114+
if (!locked) {
115+
return;
116+
}
117+
locked = false;
118+
notifyAll();
119+
}
89120
LOGGER.debug(
90121
ManagerMessages.PIPETASKCOORDINATOR_LOCK_RELEASED_BY_THREAD,
91122
Thread.currentThread().getName());
92123
}
93124

94-
public boolean isLocked() {
95-
return semaphore.availablePermits() == 0;
125+
public synchronized boolean isLocked() {
126+
return locked;
96127
}
97128
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.iotdb.confignode.manager.pipe.agent.runtime;
21+
22+
import org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant;
23+
import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters;
24+
25+
import org.junit.Assert;
26+
import org.junit.Test;
27+
28+
import java.util.HashMap;
29+
30+
public class PipeConfigRegionListenerTest {
31+
32+
@Test
33+
public void testRepeatedDecreaseDoesNotBreakFutureIncrease() throws Exception {
34+
PipeConfigRegionListener listener = new PipeConfigRegionListener();
35+
PipeParameters parameters =
36+
new PipeParameters(
37+
new HashMap<String, String>() {
38+
{
39+
put(PipeSourceConstant.EXTRACTOR_INCLUSION_KEY, "schema.database.create");
40+
}
41+
});
42+
43+
listener.increaseReference(parameters);
44+
Assert.assertTrue(listener.listener().isOpened());
45+
46+
listener.decreaseReference(parameters);
47+
Assert.assertFalse(listener.listener().isOpened());
48+
49+
listener.decreaseReference(parameters);
50+
Assert.assertFalse(listener.listener().isOpened());
51+
52+
listener.increaseReference(parameters);
53+
Assert.assertTrue(listener.listener().isOpened());
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.iotdb.confignode.manager.pipe.coordinator.task;
21+
22+
import org.junit.Assert;
23+
import org.junit.Test;
24+
25+
import java.util.concurrent.TimeUnit;
26+
import java.util.concurrent.atomic.AtomicBoolean;
27+
28+
public class PipeTaskCoordinatorLockTest {
29+
30+
@Test
31+
public void testRepeatedUnlockDoesNotIncreaseCapacity() throws Exception {
32+
PipeTaskCoordinatorLock lock = new PipeTaskCoordinatorLock();
33+
34+
lock.lock();
35+
lock.unlock();
36+
lock.unlock();
37+
Assert.assertTrue(lock.tryLock());
38+
39+
AtomicBoolean acquired = new AtomicBoolean(false);
40+
Thread waiter =
41+
new Thread(
42+
() -> {
43+
lock.lock();
44+
acquired.set(true);
45+
});
46+
waiter.start();
47+
waitUntilState(waiter, Thread.State.WAITING);
48+
Assert.assertFalse(acquired.get());
49+
50+
lock.unlock();
51+
waiter.join(TimeUnit.SECONDS.toMillis(5));
52+
Assert.assertFalse(waiter.isAlive());
53+
Assert.assertTrue(acquired.get());
54+
lock.unlock();
55+
}
56+
57+
@Test
58+
public void testInterruptedLockDoesNotReturnBeforeAcquired() throws Exception {
59+
PipeTaskCoordinatorLock lock = new PipeTaskCoordinatorLock();
60+
AtomicBoolean acquired = new AtomicBoolean(false);
61+
AtomicBoolean interruptedAfterLock = new AtomicBoolean(false);
62+
63+
lock.lock();
64+
Thread waiter =
65+
new Thread(
66+
() -> {
67+
Thread.currentThread().interrupt();
68+
lock.lock();
69+
acquired.set(true);
70+
interruptedAfterLock.set(Thread.currentThread().isInterrupted());
71+
});
72+
waiter.start();
73+
waitUntilState(waiter, Thread.State.WAITING);
74+
Assert.assertFalse(acquired.get());
75+
76+
lock.unlock();
77+
waiter.join(TimeUnit.SECONDS.toMillis(5));
78+
Assert.assertFalse(waiter.isAlive());
79+
Assert.assertTrue(acquired.get());
80+
Assert.assertTrue(interruptedAfterLock.get());
81+
lock.unlock();
82+
}
83+
84+
@Test
85+
public void testInterruptedTryLockDoesNotAcquire() {
86+
PipeTaskCoordinatorLock lock = new PipeTaskCoordinatorLock();
87+
88+
Thread.currentThread().interrupt();
89+
try {
90+
Assert.assertFalse(lock.tryLock());
91+
Assert.assertFalse(lock.isLocked());
92+
Assert.assertTrue(Thread.currentThread().isInterrupted());
93+
} finally {
94+
Thread.interrupted();
95+
}
96+
}
97+
98+
private void waitUntilState(Thread thread, Thread.State expectedState) throws Exception {
99+
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5);
100+
while (System.nanoTime() < deadline) {
101+
if (thread.getState() == expectedState) {
102+
return;
103+
}
104+
Thread.sleep(10);
105+
}
106+
Assert.assertEquals(expectedState, thread.getState());
107+
}
108+
}

iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCache.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -374,8 +374,10 @@ private Map<String, Map<String, Long>> mayGetTableInPreUpdateMap(
374374
private Map<String, Map<String, TsTable>> getTablesInConfigNode(
375375
final Map<String, Map<String, Long>> tableInput) {
376376
Map<String, Map<String, TsTable>> result = Collections.emptyMap();
377+
boolean acquired = false;
377378
try {
378379
fetchTableSemaphore.acquire();
380+
acquired = true;
379381
final TFetchTableResp resp =
380382
ClusterConfigTaskExecutor.getInstance()
381383
.fetchTables(
@@ -388,11 +390,11 @@ private Map<String, Map<String, TsTable>> getTablesInConfigNode(
388390
} catch (final InterruptedException e) {
389391
Thread.currentThread().interrupt();
390392
LOGGER.warn(DataNodeSchemaMessages.INTERRUPTED_ACQUIRE_SEMAPHORE_GET_TABLES);
391-
} catch (final Exception e) {
392-
fetchTableSemaphore.release();
393-
throw e;
393+
} finally {
394+
if (acquired) {
395+
fetchTableSemaphore.release();
396+
}
394397
}
395-
fetchTableSemaphore.release();
396398
return result;
397399
}
398400

iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/concurrent/FiniteSemaphore.java

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,12 @@
2020

2121
import org.apache.iotdb.db.i18n.DataNodeMiscMessages;
2222

23-
import java.util.concurrent.Semaphore;
24-
2523
/**
2624
* FiniteSemaphore defines a special Semaphore that the upper limit of permit is capacity. If
2725
* permits exceed the capacity, the release request will be ignored.
2826
*/
2927
public class FiniteSemaphore {
3028
private final int capacity;
31-
private final Semaphore semaphore;
3229
private int permit;
3330

3431
public FiniteSemaphore(int capacity, int permit) {
@@ -39,23 +36,20 @@ public FiniteSemaphore(int capacity, int permit) {
3936
throw new IllegalArgumentException(DataNodeMiscMessages.CAPACITY_LARGER_THAN_INITIAL_PERMITS);
4037
}
4138
this.capacity = capacity;
42-
this.semaphore = new Semaphore(permit);
4339
this.permit = permit;
4440
}
4541

46-
public void release() {
47-
synchronized (this) {
48-
if (permit < capacity) {
49-
permit++;
50-
semaphore.release();
51-
}
42+
public synchronized void release() {
43+
if (permit < capacity) {
44+
permit++;
45+
notifyAll();
5246
}
5347
}
5448

55-
public void acquire() throws InterruptedException {
56-
semaphore.acquire();
57-
synchronized (this) {
58-
permit--;
49+
public synchronized void acquire() throws InterruptedException {
50+
while (permit == 0) {
51+
wait();
5952
}
53+
permit--;
6054
}
6155
}

0 commit comments

Comments
 (0)