Skip to content

Commit 8d34003

Browse files
Merge pull request #388 from neo4j-contrib/0.28-neo4j-4.3
Port Spatial 0.28 to Neo4j 4.3
2 parents 9131753 + 8fd4afc commit 8d34003

15 files changed

+115
-65
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ This has meant that the spatial library needed a major refactoring to work with
8181
0.28.0 solves this by using different indexes, and keeping all imports completely separate.
8282
The more complex problems of importing newer versions, and stitching together overlapping areas, are not
8383
yet solved.
84+
* Neo4j 4.3 has an issue with leaking RelationshipTraversalCursor, and we needed to do some workarounds to
85+
avoid this issue, usually by exhausting the iterator, which can have a higher performance cost in some cases.
86+
Version 0.28.1 includes this fix.
8487

8588
Consequences of the port to Neo4j 4.x:
8689

@@ -355,6 +358,7 @@ The Neo4j Spatial Plugin is available for inclusion in the server version of Neo
355358
* [v0.27.1 for Neo4j 4.1.7](https://github.com/neo4j-contrib/m2/blob/master/releases/org/neo4j/neo4j-spatial/0.27.1-neo4j-4.1.7/neo4j-spatial-0.27.1-neo4j-4.1.7-server-plugin.jar?raw=true)
356359
* [v0.27.2 for Neo4j 4.2.3](https://github.com/neo4j-contrib/m2/blob/master/releases/org/neo4j/neo4j-spatial/0.27.2-neo4j-4.2.3/neo4j-spatial-0.27.2-neo4j-4.2.3-server-plugin.jar?raw=true)
357360
* [v0.28.0 for Neo4j 4.2.3](https://github.com/neo4j-contrib/m2/blob/master/releases/org/neo4j/neo4j-spatial/0.28.0-neo4j-4.2.3/neo4j-spatial-0.28.0-neo4j-4.2.3-server-plugin.jar?raw=true)
361+
* [v0.28.1 for Neo4j 4.3.10](https://github.com/neo4j-contrib/m2/blob/master/releases/org/neo4j/neo4j-spatial/0.28.1-neo4j-4.3.10/neo4j-spatial-0.28.1-neo4j-4.3.10-server-plugin.jar?raw=true)
358362

359363
For versions up to 0.15-neo4j-2.3.4:
360364

@@ -471,7 +475,7 @@ Add the following repositories and dependency to your project's pom.xml:
471475
<dependency>
472476
<groupId>org.neo4j</groupId>
473477
<artifactId>neo4j-spatial</artifactId>
474-
<version>0.28.0-neo4j-4.2.3</version>
478+
<version>0.28.1-neo4j-4.3.10</version>
475479
</dependency>
476480
~~~
477481

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
33
<properties>
4-
<neo4j.version>4.2.3</neo4j.version>
4+
<neo4j.version>4.3.10</neo4j.version>
55
<lucene.version>8.2.0</lucene.version>
66
<!-- make sure lucene version is the same as the one the current neo4j depends on -->
77
<neo4j.java.version>11</neo4j.java.version>
@@ -23,7 +23,7 @@
2323
<modelVersion>4.0.0</modelVersion>
2424
<artifactId>neo4j-spatial</artifactId>
2525
<groupId>org.neo4j</groupId>
26-
<version>0.28.0-neo4j-4.2.3</version>
26+
<version>0.28.1-neo4j-4.3.10</version>
2727
<name>Neo4j - Spatial Components</name>
2828
<description>Spatial utilities and components for Neo4j</description>
2929
<url>http://components.neo4j.org/${project.artifactId}/${project.version}</url>

src/main/java/org/neo4j/gis/spatial/index/LayerSpaceFillingCurvePointIndex.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@
3535
import org.neo4j.internal.schema.IndexDescriptor;
3636
import org.neo4j.internal.schema.IndexType;
3737
import org.neo4j.internal.schema.SchemaDescriptor;
38-
import org.neo4j.io.pagecache.tracing.cursor.PageCursorTracer;
38+
import org.neo4j.io.pagecache.context.CursorContext;
3939
import org.neo4j.kernel.api.KernelTransaction;
4040
import org.neo4j.kernel.impl.core.NodeEntity;
41-
import org.neo4j.kernel.impl.coreapi.internal.NodeCursorResourceIterator;
41+
import org.neo4j.kernel.impl.coreapi.internal.CursorIterator;
4242
import org.neo4j.memory.EmptyMemoryTracker;
4343
import org.opengis.referencing.crs.CoordinateReferenceSystem;
4444
import org.opengis.referencing.cs.CoordinateSystemAxis;
@@ -48,6 +48,7 @@
4848
import java.util.List;
4949

5050
import static org.neo4j.internal.helpers.collection.Iterators.emptyResourceIterator;
51+
import static org.neo4j.kernel.api.ResourceTracker.EMPTY_RESOURCE_TRACKER;
5152

5253
public abstract class LayerSpaceFillingCurvePointIndex extends ExplicitIndexBackedPointIndex<Long> {
5354

@@ -120,13 +121,13 @@ public Iterator<Node> search(KernelTransaction ktx, Label label, String property
120121
int propId = ktx.tokenRead().propertyKey(propertyKey);
121122
ArrayList<Iterator<Node>> results = new ArrayList<>();
122123
for(SpaceFillingCurve.LongRange range:tiles) {
123-
IndexQuery indexQuery = IndexQuery.range(propId, range.min, true, range.max, true);
124+
PropertyIndexQuery indexQuery = PropertyIndexQuery.range(propId, range.min, true, range.max, true);
124125
results.add(nodesByLabelAndProperty(ktx, labelId, indexQuery));
125126
}
126127
return Iterators.concat(results.iterator());
127128
}
128129

129-
private ResourceIterator<Node> nodesByLabelAndProperty(KernelTransaction transaction, int labelId, IndexQuery query )
130+
private ResourceIterator<Node> nodesByLabelAndProperty(KernelTransaction transaction, int labelId, PropertyIndexQuery query )
130131
{
131132
Read read = transaction.dataRead();
132133

@@ -146,11 +147,11 @@ private ResourceIterator<Node> nodesByLabelAndProperty(KernelTransaction transac
146147
// Ha! We found an index - let's use it to find matching nodes
147148
try
148149
{
149-
NodeValueIndexCursor cursor = transaction.cursors().allocateNodeValueIndexCursor(PageCursorTracer.NULL, EmptyMemoryTracker.INSTANCE);
150+
NodeValueIndexCursor cursor = transaction.cursors().allocateNodeValueIndexCursor(CursorContext.NULL, EmptyMemoryTracker.INSTANCE);
150151
IndexReadSession indexSession = read.indexReadSession( index );
151152
read.nodeIndexSeek( indexSession, cursor, IndexQueryConstraints.unordered(false), query );
152153

153-
return new NodeCursorResourceIterator<>( cursor, (id) -> new NodeEntity(transaction.internalTransaction(), id) );
154+
return new CursorIterator<>(cursor, NodeIndexCursor::nodeReference, (c) -> new NodeEntity(transaction.internalTransaction(), c.nodeReference()), EMPTY_RESOURCE_TRACKER);
154155
}
155156
catch ( KernelException e )
156157
{

src/main/java/org/neo4j/gis/spatial/osm/OSMDataset.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.locationtech.jts.geom.Envelope;
2424
import org.locationtech.jts.geom.Geometry;
2525
import org.neo4j.gis.spatial.*;
26+
import org.neo4j.gis.spatial.utilities.RelationshipTraversal;
2627
import org.neo4j.graphdb.Direction;
2728
import org.neo4j.graphdb.Node;
2829
import org.neo4j.graphdb.Relationship;
@@ -144,8 +145,7 @@ public Node getUser(Node nodeWayOrChangeset) {
144145
.relationships(OSMRelation.CHANGESET, Direction.OUTGOING)
145146
.relationships(OSMRelation.USER, Direction.OUTGOING)
146147
.evaluator(Evaluators.includeWhereLastRelationshipTypeIs(OSMRelation.USER));
147-
Iterator<Node> results = td.traverse(nodeWayOrChangeset).nodes().iterator();
148-
return results.hasNext() ? results.next() : null;
148+
return RelationshipTraversal.getFirstNode(td.traverse(nodeWayOrChangeset).nodes());
149149
}
150150

151151
public Way getWayFromId(Transaction tx, long id) {
@@ -161,8 +161,8 @@ public Way getWayFrom(Node osmNodeOrWayNodeOrGeomNode) {
161161
.relationships(OSMRelation.GEOM, Direction.INCOMING)
162162
.evaluator(path -> path.endNode().hasProperty("way_osm_id") ? Evaluation.INCLUDE_AND_PRUNE
163163
: Evaluation.EXCLUDE_AND_CONTINUE);
164-
Iterator<Node> results = td.traverse(osmNodeOrWayNodeOrGeomNode).nodes().iterator();
165-
return results.hasNext() ? new Way(results.next()) : null;
164+
Node node = RelationshipTraversal.getFirstNode(td.traverse(osmNodeOrWayNodeOrGeomNode).nodes());
165+
return node != null ? new Way(node) : null;
166166
}
167167

168168
public class OSMNode {

src/main/java/org/neo4j/gis/spatial/pipes/impl/AbstractPipe.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ public void reset() {
4545
if (this.starts instanceof Pipe) {
4646
((Pipe) this.starts).reset();
4747
}
48+
if (this.starts instanceof LastElementIterator) {
49+
((LastElementIterator) this.starts).reset();
50+
}
4851
this.nextEnd = null;
4952
this.currentEnd = null;
5053
this.available = false;

src/main/java/org/neo4j/gis/spatial/pipes/impl/LastElementIterator.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.neo4j.gis.spatial.pipes.impl;
22

3+
import org.neo4j.gis.spatial.utilities.RelationshipTraversal;
4+
35
import java.util.Iterator;
46

57
public class LastElementIterator<T> implements Iterator<T> {
@@ -26,4 +28,14 @@ public void remove() {
2628
public T lastElement() {
2729
return lastElement;
2830
}
31+
32+
/**
33+
* Work around bug in Neo4j 4.3 with leaked RelationshipTraversalCursor
34+
*/
35+
public void reset() {
36+
// TODO: rather try get deeper into the underlying index and close resources instead of exhausting the iterator
37+
// The challenge is that there are many sources, all of which need to be made closable, and that is very hard
38+
// to achieve in a generic way without a full-stack code change.
39+
RelationshipTraversal.exhaustIterator(source);
40+
}
2941
}

src/main/java/org/neo4j/gis/spatial/pipes/impl/Pipeline.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ public int size() {
127127
}
128128

129129
public void reset() {
130+
this.startPipe.reset(); // Clear incoming state to avoid bug in Neo4j 4.3 with leaked RelationshipTraversalCursor
130131
this.endPipe.reset();
131132
}
132133

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (c) 2010-2022 "Neo4j,"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j Spatial.
6+
*
7+
* Neo4j is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*/
20+
package org.neo4j.gis.spatial.utilities;
21+
22+
import org.neo4j.graphdb.*;
23+
24+
import java.util.Iterator;
25+
26+
/**
27+
* Neo4j 4.3 introduced some bugs around closing RelationshipTraversalCursor.
28+
* This class provides alternative implementations of suspicious methods to work around that issue.
29+
*/
30+
public class RelationshipTraversal {
31+
/**
32+
* Normally just calling iterator.next() once should work, but the bug in Neo4j 4.3 results in leaked cursors
33+
* if this iterator comes from the traversal framework, so this code exhausts the traverser and returns the first
34+
* node found. For cases where many results could be found, this is expensive. Try to use only when one or few results
35+
* are likely.
36+
*/
37+
public static Node getFirstNode(Iterable<Node> nodes) {
38+
Node found = null;
39+
for (Node node : nodes) {
40+
if (found == null) found = node;
41+
}
42+
return found;
43+
}
44+
45+
/**
46+
* Some code has facilities for closing resource at a high level, but the underlying resources are only
47+
* Iterators, with no access to the original sources and no way to close the resources properly.
48+
* So to avoid the Neo4j 4.3 bug with leaked RelationshipTraversalCursor, we need to exhaust the iterator.
49+
*/
50+
public static void exhaustIterator(Iterator source) {
51+
while (source.hasNext()) {
52+
source.next();
53+
}
54+
}
55+
}

src/test/java/org/neo4j/gis/spatial/Neo4jTestCase.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,17 @@
2323
import org.junit.Before;
2424
import org.junit.Rule;
2525
import org.neo4j.configuration.Config;
26+
import org.neo4j.configuration.GraphDatabaseInternalSettings;
2627
import org.neo4j.configuration.GraphDatabaseSettings;
2728
import org.neo4j.dbms.api.DatabaseManagementService;
28-
import org.neo4j.dbms.api.DatabaseManagementServiceBuilder;
2929
import org.neo4j.gis.spatial.procedures.SpatialProcedures;
3030
import org.neo4j.graphdb.GraphDatabaseService;
3131
import org.neo4j.io.fs.FileUtils;
3232
import org.neo4j.io.layout.DatabaseLayout;
3333
import org.neo4j.io.layout.Neo4jLayout;
3434
import org.neo4j.kernel.api.procedure.GlobalProcedures;
3535
import org.neo4j.kernel.internal.GraphDatabaseAPI;
36+
import org.neo4j.test.TestDatabaseManagementServiceBuilder;
3637
import org.neo4j.test.rule.fs.EphemeralFileSystemRule;
3738

3839
import java.io.File;
@@ -57,6 +58,7 @@ public abstract class Neo4jTestCase {
5758
//NORMAL_CONFIG.put( GraphDatabaseSettings.strings_mapped_memory_size.name(), "200M" );
5859
//NORMAL_CONFIG.put( GraphDatabaseSettings.arrays_mapped_memory_size.name(), "0M" );
5960
NORMAL_CONFIG.put(GraphDatabaseSettings.pagecache_memory.name(), "200M");
61+
NORMAL_CONFIG.put(GraphDatabaseInternalSettings.trace_cursors.name(), "true");
6062
}
6163

6264
static final Map<String, String> LARGE_CONFIG = new HashMap<>();
@@ -100,7 +102,7 @@ protected void setUp(boolean deleteDb) throws Exception {
100102
if (largeMode != null && largeMode.equalsIgnoreCase("true")) {
101103
config = LARGE_CONFIG;
102104
}
103-
databases = new DatabaseManagementServiceBuilder(getDbPath()).setConfigRaw(config).build();
105+
databases = new TestDatabaseManagementServiceBuilder(getDbPath()).setConfigRaw(config).build();
104106
graphDb = databases.database(DEFAULT_DATABASE_NAME);
105107
((GraphDatabaseAPI) graphDb).getDependencyResolver().resolveDependency(GlobalProcedures.class).registerProcedure(SpatialProcedures.class);
106108
}

src/test/java/org/neo4j/gis/spatial/OsmAnalysisTest.java

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -112,40 +112,8 @@ public void runTest() throws Exception {
112112
runAnalysis(layerName, years, days);
113113
}
114114

115-
private DatabaseManagementService databases;
116-
private GraphDatabaseService db;
117-
118-
protected GraphDatabaseService graphDb() {
119-
return db == null ? super.graphDb() : db;
120-
}
121-
122-
protected void shutdownDatabase() {
123-
if (db != null) {
124-
databases.shutdown();
125-
databases = null;
126-
db = null;
127-
}
128-
}
129-
130-
protected SpatialDatabaseService setDataset(String dataset) {
131-
if (db != null) {
132-
shutdownDatabase();
133-
}
134-
File dbDir = new File("var", dataset);
135-
if (dbDir.exists()) {
136-
try {
137-
FileUtils.deleteDirectory(dbDir);
138-
} catch (IOException e) {
139-
System.out.println("Failed to delete previous database directory '" + dbDir + "': " + e.getMessage());
140-
}
141-
}
142-
databases = new TestDatabaseManagementServiceBuilder(dbDir.toPath()).impermanent().build();
143-
db = databases.database(DEFAULT_DATABASE_NAME);
144-
return new SpatialDatabaseService(new IndexManager((GraphDatabaseAPI) db, SecurityContext.AUTH_DISABLED));
145-
}
146-
147115
protected void runAnalysis(String osm, int years, int days) throws Exception {
148-
SpatialDatabaseService spatial = setDataset(osm);
116+
SpatialDatabaseService spatial = new SpatialDatabaseService(new IndexManager((GraphDatabaseAPI) graphDb(), SecurityContext.AUTH_DISABLED));
149117
boolean alreadyImported;
150118
try (Transaction tx = graphDb().beginTx()) {
151119
alreadyImported = spatial.getLayer(tx, osm) != null;
@@ -155,11 +123,10 @@ protected void runAnalysis(String osm, int years, int days) throws Exception {
155123
runImport(osm, usePoints);
156124
}
157125
testAnalysis2(osm, years, days);
158-
shutdownDatabase();
159126
}
160127

161128
public void testAnalysis2(String osm, int years, int days) throws IOException {
162-
SpatialDatabaseService spatial = new SpatialDatabaseService(new IndexManager((GraphDatabaseAPI) db, SecurityContext.AUTH_DISABLED));
129+
SpatialDatabaseService spatial = new SpatialDatabaseService(new IndexManager((GraphDatabaseAPI) graphDb(), SecurityContext.AUTH_DISABLED));
163130
LinkedHashMap<DynamicLayerConfig, Long> slides = new LinkedHashMap<>();
164131
Map<String, User> userIndex = new HashMap<>();
165132
int user_rank = 1;
@@ -279,7 +246,7 @@ public void testAnalysis(String osm) throws Exception {
279246
Map<String, User> userIndex = collectUserChangesetData(usersNode);
280247
SortedSet<User> topTen = getTopTen(userIndex);
281248

282-
SpatialDatabaseService spatial = new SpatialDatabaseService(new IndexManager((GraphDatabaseAPI) db, SecurityContext.AUTH_DISABLED));
249+
SpatialDatabaseService spatial = new SpatialDatabaseService(new IndexManager((GraphDatabaseAPI) graphDb(), SecurityContext.AUTH_DISABLED));
283250
layers = exportPoints(tx, osm, spatial, topTen);
284251

285252
layers = removeEmptyLayers(tx, layers);

0 commit comments

Comments
 (0)