Skip to content

Commit 18fe87f

Browse files
authored
Merge branch 'master' into task/improve-condition-query-semantics
2 parents b16f5f1 + c3f56b5 commit 18fe87f

33 files changed

Lines changed: 792 additions & 87 deletions

File tree

.github/workflows/docker-build-ci.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,8 @@ jobs:
4545

4646
- name: Build ${{ matrix.dockerfile }}
4747
run: |
48-
docker build -f ${{ matrix.dockerfile }} .
48+
IMAGE_ID=$(docker build -q -f ${{ matrix.dockerfile }} .)
49+
echo "Built: $IMAGE_ID"
50+
HC=$(docker inspect --format='{{json .Config.Healthcheck}}' "$IMAGE_ID")
51+
echo "Healthcheck: $HC"
52+
[[ "$HC" != "null" ]] || { echo "ERROR: HEALTHCHECK missing in ${{ matrix.dockerfile }}"; exit 1; }

.github/workflows/pd-store-ci.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,30 @@ jobs:
107107
run: |
108108
mvn clean package -U -Dmaven.javadoc.skip=true -Dmaven.test.skip=true -ntp --fail-at-end
109109
110+
- name: Check startup test prerequisites (PD)
111+
id: pd-preflight
112+
run: |
113+
for tool in lsof curl java; do
114+
if ! command -v "$tool" >/dev/null 2>&1; then
115+
echo "can_run=false" >> "$GITHUB_OUTPUT"
116+
echo "skip_reason=missing tool: $tool" >> "$GITHUB_OUTPUT"
117+
exit 0
118+
fi
119+
done
120+
echo "can_run=true" >> "$GITHUB_OUTPUT"
121+
110122
- name: Run start-hugegraph-pd.sh foreground mode tests
123+
if: steps.pd-preflight.outputs.can_run == 'true'
111124
run: |
112125
VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
113126
PD_DIR=hugegraph-pd/apache-hugegraph-pd-$VERSION/
114127
$TRAVIS_DIR/test-start-hugegraph-pd.sh $PD_DIR
115128
129+
- name: Startup tests skipped (missing prerequisites)
130+
if: steps.pd-preflight.outputs.can_run == 'false'
131+
run: |
132+
echo "::notice::PD startup tests skipped — ${{ steps.pd-preflight.outputs.skip_reason }}"
133+
116134
- name: Prepare env and service
117135
run: |
118136
$TRAVIS_DIR/start-pd.sh
@@ -169,12 +187,36 @@ jobs:
169187
run: |
170188
mvn clean package -U -Dmaven.javadoc.skip=true -Dmaven.test.skip=true -ntp --fail-at-end
171189
190+
- name: Check startup test prerequisites (Store)
191+
id: store-preflight
192+
run: |
193+
for tool in lsof curl java; do
194+
if ! command -v "$tool" >/dev/null 2>&1; then
195+
echo "can_run=false" >> "$GITHUB_OUTPUT"
196+
echo "skip_reason=missing tool: $tool" >> "$GITHUB_OUTPUT"
197+
exit 0
198+
fi
199+
done
200+
LIMIT_N=$(ulimit -n)
201+
if [[ "$LIMIT_N" != "unlimited" ]] && (( LIMIT_N < 1024 )); then
202+
echo "can_run=false" >> "$GITHUB_OUTPUT"
203+
echo "skip_reason=ulimit -n is $LIMIT_N (store requires >= 1024)" >> "$GITHUB_OUTPUT"
204+
exit 0
205+
fi
206+
echo "can_run=true" >> "$GITHUB_OUTPUT"
207+
172208
- name: Run start-hugegraph-store.sh foreground mode tests
209+
if: steps.store-preflight.outputs.can_run == 'true'
173210
run: |
174211
VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
175212
STORE_DIR=hugegraph-store/apache-hugegraph-store-$VERSION/
176213
$TRAVIS_DIR/test-start-hugegraph-store.sh $STORE_DIR
177214
215+
- name: Startup tests skipped (missing prerequisites)
216+
if: steps.store-preflight.outputs.can_run == 'false'
217+
run: |
218+
echo "::notice::Store startup tests skipped — ${{ steps.store-preflight.outputs.skip_reason }}"
219+
178220
- name: Prepare env and service
179221
run: |
180222
$TRAVIS_DIR/start-pd.sh

.github/workflows/server-ci.yml

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,32 @@ jobs:
7070
run: |
7171
mvn clean compile -U -Dmaven.javadoc.skip=true -ntp
7272
73-
- name: Run start-hugegraph.sh foreground mode tests
73+
- name: Check startup test prerequisites
74+
id: server-preflight
7475
if: ${{ env.BACKEND == 'rocksdb' }}
76+
run: |
77+
for tool in lsof crontab curl java; do
78+
if ! command -v "$tool" >/dev/null 2>&1; then
79+
echo "can_run=false" >> "$GITHUB_OUTPUT"
80+
echo "skip_reason=missing tool: $tool" >> "$GITHUB_OUTPUT"
81+
exit 0
82+
fi
83+
done
84+
echo "can_run=true" >> "$GITHUB_OUTPUT"
85+
86+
- name: Run start-hugegraph.sh foreground mode tests
87+
if: ${{ env.BACKEND == 'rocksdb' && steps.server-preflight.outputs.can_run == 'true' }}
7588
run: |
7689
mvn package -Dmaven.test.skip=true -pl hugegraph-server/hugegraph-dist -am -ntp
7790
VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
7891
SERVER_DIR=hugegraph-server/apache-hugegraph-server-$VERSION/
7992
$TRAVIS_DIR/test-start-hugegraph.sh $SERVER_DIR
8093
94+
- name: Startup tests skipped (missing prerequisites)
95+
if: ${{ env.BACKEND == 'rocksdb' && steps.server-preflight.outputs.can_run == 'false' }}
96+
run: |
97+
echo "::notice::Server startup tests skipped — ${{ steps.server-preflight.outputs.skip_reason }}"
98+
8199
- name: Run unit test
82100
run: |
83101
$TRAVIS_DIR/run-unit-test.sh $BACKEND

hugegraph-pd/Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ RUN apt-get -q update \
5252
curl \
5353
lsof \
5454
vim \
55-
cron \
5655
&& apt-get clean \
5756
&& rm -rf /var/lib/apt/lists/*
5857

@@ -63,5 +62,8 @@ RUN chmod 755 ./docker-entrypoint.sh
6362
EXPOSE 8620
6463
VOLUME /hugegraph-pd
6564

65+
HEALTHCHECK --interval=15s --timeout=10s --start-period=90s --retries=3 \
66+
CMD curl -fsS http://localhost:8620/v1/health >/dev/null
67+
6668
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
6769
CMD ["./docker-entrypoint.sh"]

hugegraph-pd/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,13 @@ bin/stop-hugegraph-pd.sh
7878
#### Startup Options
7979

8080
```bash
81-
bin/start-hugegraph-pd.sh [-g GC_TYPE] [-j "JVM_OPTIONS"] [-y ENABLE_OTEL]
81+
bin/start-hugegraph-pd.sh [-g GC_TYPE] [-j "JVM_OPTIONS"] [-y ENABLE_OTEL] [-d DAEMON]
8282
```
8383

8484
- `-g`: GC type (`g1` or `ZGC`, default: `g1`)
8585
- `-j`: Custom JVM options (e.g., `-j "-Xmx4g -Xms4g"`)
8686
- `-y`: Enable OpenTelemetry tracing (`true` or `false`, default: `false`)
87+
- `-d`: Daemon mode (`true` = daemon, `false` = foreground; default: `true`). Set to `false` when running under Docker or a process supervisor so the container exits if Java dies.
8788

8889
### Configuration
8990

hugegraph-pd/hg-pd-dist/docker/docker-entrypoint.sh

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,5 +82,4 @@ log " pd.initial-store-list=${HG_PD_INITIAL_STORE_LIST}"
8282
log " pd.initial-store-count=${HG_PD_INITIAL_STORE_COUNT}"
8383
log " pd.data-path=${HG_PD_DATA_PATH}"
8484

85-
./bin/start-hugegraph-pd.sh -j "${JAVA_OPTS:-}"
86-
tail -f /dev/null
85+
./bin/start-hugegraph-pd.sh -d false -j "${JAVA_OPTS:-}"

hugegraph-server/Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ RUN apt-get -q update \
5353
curl \
5454
lsof \
5555
vim \
56-
cron \
5756
&& apt-get clean \
5857
&& rm -rf /var/lib/apt/lists/* \
5958
&& sed -i "s/^restserver.url.*$/restserver.url=http:\/\/0.0.0.0:8080/g" ./conf/rest-server.properties
@@ -67,5 +66,8 @@ RUN chmod 755 ./docker-entrypoint.sh
6766
EXPOSE 8080
6867
VOLUME /hugegraph-server
6968

69+
HEALTHCHECK --interval=15s --timeout=10s --start-period=90s --retries=3 \
70+
CMD curl -fsS http://localhost:8080/versions >/dev/null
71+
7072
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
7173
CMD ["./docker-entrypoint.sh"]

hugegraph-server/Dockerfile-hstore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ RUN apt-get -q update \
5555
curl \
5656
lsof \
5757
vim \
58-
cron \
5958
&& apt-get clean \
6059
&& rm -rf /var/lib/apt/lists/* \
6160
&& sed -i "s/^restserver.url.*$/restserver.url=http:\/\/0.0.0.0:8080/g" ./conf/rest-server.properties
@@ -69,5 +68,8 @@ RUN chmod 755 ./docker-entrypoint.sh
6968
EXPOSE 8080
7069
VOLUME /hugegraph-server
7170

71+
HEALTHCHECK --interval=15s --timeout=10s --start-period=90s --retries=3 \
72+
CMD curl -fsS http://localhost:8080/versions >/dev/null
73+
7274
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
7375
CMD ["./docker-entrypoint.sh"]

hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/StandardHugeGraph.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.apache.hugegraph.backend.id.SnowflakeIdGenerator;
4949
import org.apache.hugegraph.backend.query.Query;
5050
import org.apache.hugegraph.backend.serializer.AbstractSerializer;
51+
import org.apache.hugegraph.backend.serializer.BytesBuffer;
5152
import org.apache.hugegraph.backend.serializer.SerializerFactory;
5253
import org.apache.hugegraph.backend.store.BackendFeatures;
5354
import org.apache.hugegraph.backend.store.BackendProviderFactory;
@@ -231,15 +232,22 @@ public StandardHugeGraph(HugeConfig config) {
231232
this.readMode = GraphReadMode.OLTP_ONLY;
232233
this.schedulerType = config.get(CoreOptions.SCHEDULER_TYPE);
233234

234-
LockUtil.init(this.spaceGraphName());
235-
235+
// Init process-wide static configs before lock, so that validation
236+
// failures won't leave stale lock groups in LockManager.
237+
boolean explicitBufferCapacity = config.containsKey(
238+
CoreOptions.SERIALIZER_BUFFER_MAX_CAPACITY.name());
239+
BytesBuffer.initMaxBufferCapacity(
240+
config.get(CoreOptions.SERIALIZER_BUFFER_MAX_CAPACITY),
241+
explicitBufferCapacity);
236242
MemoryManager.setMemoryMode(
237243
MemoryManager.MemoryMode.fromValue(config.get(CoreOptions.MEMORY_MODE)));
238244
MemoryManager.setMaxMemoryCapacityInBytes(config.get(CoreOptions.MAX_MEMORY_CAPACITY));
239245
MemoryManager.setMaxMemoryCapacityForOneQuery(
240246
config.get(CoreOptions.ONE_QUERY_MAX_MEMORY_CAPACITY));
241247
RoundUtil.setAlignment(config.get(CoreOptions.MEMORY_ALIGNMENT));
242248

249+
LockUtil.init(this.spaceGraphName());
250+
243251
try {
244252
this.storeProvider = this.loadStoreProvider();
245253
} catch (Exception e) {

hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/cache/CachedGraphTransaction.java

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.apache.hugegraph.backend.query.QueryResults;
3636
import org.apache.hugegraph.backend.store.BackendMutation;
3737
import org.apache.hugegraph.backend.store.BackendStore;
38+
import org.apache.hugegraph.backend.store.BackendStoreProvider;
3839
import org.apache.hugegraph.backend.store.ram.RamTable;
3940
import org.apache.hugegraph.backend.tx.GraphTransaction;
4041
import org.apache.hugegraph.config.CoreOptions;
@@ -70,12 +71,22 @@ public final class CachedGraphTransaction extends GraphTransaction {
7071
private static final ConcurrentMap<String, CacheListenerHolder>
7172
GRAPH_CACHE_EVENT_LISTENERS = new ConcurrentHashMap<>();
7273

74+
/*
75+
* Same ref-counted lifecycle for the store event listener registered
76+
* on the BackendStoreProvider; see StoreListenerHolder.
77+
*
78+
* Replaces the removed protected static storeEventListenStatus field
79+
* that previously tracked store-listen state on GraphTransaction.
80+
*/
81+
private static final ConcurrentMap<String, StoreListenerHolder>
82+
STORE_EVENT_LISTENERS = new ConcurrentHashMap<>();
83+
7384
private final Cache<Id, Object> verticesCache;
7485
private final Cache<Id, Object> edgesCache;
7586

76-
private EventListener storeEventListener;
7787
private EventListener cacheEventListener;
7888
private CacheListenerHolder holder;
89+
private StoreListenerHolder storeHolder;
7990

8091
public CachedGraphTransaction(HugeGraphParams graph, BackendStore store) {
8192
super(graph, store);
@@ -135,7 +146,7 @@ private void listenChanges() {
135146
Set<String> storeEvents = ImmutableSet.of(Events.STORE_INIT,
136147
Events.STORE_CLEAR,
137148
Events.STORE_TRUNCATE);
138-
this.storeEventListener = event -> {
149+
EventListener storeListener = event -> {
139150
if (storeEvents.contains(event.name())) {
140151
LOG.debug("Graph {} clear graph cache on event '{}'",
141152
this.graph(), event.name());
@@ -144,9 +155,24 @@ private void listenChanges() {
144155
}
145156
return false;
146157
};
147-
if (storeEventListenStatus.putIfAbsent(this.params().spaceGraphName(), true) == null) {
148-
this.store().provider().listen(this.storeEventListener);
149-
}
158+
BackendStoreProvider provider = this.store().provider();
159+
String graphName = this.params().spaceGraphName();
160+
StoreListenerHolder storeAcquired = STORE_EVENT_LISTENERS.compute(
161+
graphName, (key, existing) -> {
162+
if (existing == null || existing.provider != provider) {
163+
// Graph close/reopen creates a new provider for the
164+
// same graph name; replace the stale holder. Old
165+
// transactions skip decrement via identity check.
166+
if (existing != null) {
167+
existing.provider.unlisten(existing.listener);
168+
}
169+
provider.listen(storeListener);
170+
return new StoreListenerHolder(storeListener, provider);
171+
}
172+
existing.refCount++;
173+
return existing;
174+
});
175+
this.storeHolder = storeAcquired;
150176

151177
// Listen cache event: "cache"(invalid cache item)
152178
EventListener listener = event -> {
@@ -196,7 +222,6 @@ private void listenChanges() {
196222
return false;
197223
};
198224
EventHub graphEventHub = this.params().graphEventHub();
199-
String graphName = this.params().spaceGraphName();
200225
CacheListenerHolder acquired = GRAPH_CACHE_EVENT_LISTENERS.compute(
201226
graphName, (key, existing) -> {
202227
if (existing == null || existing.hub != graphEventHub) {
@@ -235,14 +260,20 @@ private void unlistenChanges() {
235260
this.holder = null;
236261
this.cacheEventListener = null;
237262
}
238-
// TODO (follow-up): storeEventListenStatus has the same owner-first
239-
// close bug this PR fixes for GRAPH_CACHE_EVENT_LISTENERS. A non-owner
240-
// transaction can remove the tracking entry, unlisten its own
241-
// never-registered storeEventListener as a no-op, and leave the
242-
// original store listener registered but untracked. Apply the same
243-
// ref-counted holder pattern in a follow-up PR.
244-
if (storeEventListenStatus.remove(graphName) != null) {
245-
this.store().provider().unlisten(this.storeEventListener);
263+
StoreListenerHolder storeOurs = this.storeHolder;
264+
if (storeOurs != null) {
265+
STORE_EVENT_LISTENERS.compute(graphName, (key, existing) -> {
266+
if (existing == null || existing != storeOurs) {
267+
return existing;
268+
}
269+
existing.refCount--;
270+
if (existing.refCount == 0) {
271+
existing.provider.unlisten(existing.listener);
272+
return null;
273+
}
274+
return existing;
275+
});
276+
this.storeHolder = null;
246277
}
247278
}
248279

@@ -476,4 +507,27 @@ public void removeIndex(IndexLabel indexLabel) {
476507
}
477508
}
478509
}
510+
511+
/*
512+
* Listener lifetime must cover all active transactions for the graph.
513+
* The holder is removed from the registry and unregistered from the
514+
* BackendStoreProvider only when the last transaction releases it.
515+
* Mirror of CacheListenerHolder for the store event path.
516+
*/
517+
private static final class StoreListenerHolder {
518+
519+
final EventListener listener;
520+
final BackendStoreProvider provider;
521+
// Must only be read or written inside ConcurrentMap.compute() for the
522+
// enclosing registry; ConcurrentHashMap.compute() serialises per-key
523+
// access.
524+
int refCount;
525+
526+
StoreListenerHolder(EventListener listener,
527+
BackendStoreProvider provider) {
528+
this.listener = listener;
529+
this.provider = provider;
530+
this.refCount = 1;
531+
}
532+
}
479533
}

0 commit comments

Comments
 (0)