• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gephi / graphstore / #583

04 Sep 2026 05:54PM UTC coverage: 91.138% (+0.01%) from 91.126%
#583

push

web-flow
Add a dedicated monitor for graph observer list access (#298)

* Add a dedicated monitor for graph observer list access

The observers list was previously only safe because every mutation went
through the graph write lock; this adds synchronized(observers) at each
access point so it also stays correct if auto-locking is disabled, and
marks GraphObserverImpl.destroyed volatile for cross-thread visibility.
Adds concurrency tests covering observer create/destroy racing against
version-overflow reset and view destruction.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Strengthen observer concurrency tests

An independent review found the existing concurrency tests pass even
without the observers-monitor fix, since the default auto-locking
config already serializes every access via the graph write lock — the
monitor only matters when auto-locking is disabled, which nothing
exercised. Adds a test under that config, verified to fail
(ArrayIndexOutOfBoundsException) on the pre-fix code and pass on it.
Also exercises edge-version overflow (previously only node version was
covered) and synchronizes a leftover unguarded list read.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

31 of 35 new or added lines in 3 files covered. (88.57%)

11899 of 13056 relevant lines covered (91.14%)

0.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.85
/src/main/java/org/gephi/graph/impl/GraphVersion.java
1
/*
2
 * Copyright 2012-2013 Gephi Consortium
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5
 * use this file except in compliance with the License. You may obtain a copy of
6
 * the License at
7
 *
8
 * http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
 * License for the specific language governing permissions and limitations under
14
 * the License.
15
 */
16
package org.gephi.graph.impl;
17

18
import org.gephi.graph.api.Graph;
19

20
public class GraphVersion {
21

22
    protected final Graph graph;
23
    protected int nodeVersion = Integer.MIN_VALUE + 1;
1 ✔
24
    protected int edgeVersion = Integer.MIN_VALUE + 1;
1 ✔
25

26
    public GraphVersion(Graph graph) {
1 ✔
27
        this.graph = graph;
1 ✔
28
    }
1 ✔
29

30
    public int incrementAndGetNodeVersion() {
31
        nodeVersion++;
1 ✔
32
        if (nodeVersion == Integer.MAX_VALUE) {
1 ✔
33
            nodeVersion = Integer.MIN_VALUE + 1;
1 ✔
34
            handleNodeReset();
1 ✔
35
        }
36
        return nodeVersion;
1 ✔
37
    }
38

39
    public int incrementAndGetEdgeVersion() {
40
        edgeVersion++;
1 ✔
41
        if (edgeVersion == Integer.MAX_VALUE) {
1 ✔
42
            edgeVersion = Integer.MIN_VALUE + 1;
1 ✔
43
            handleEdgeReset();
1 ✔
44
        }
45
        return edgeVersion;
1 ✔
46
    }
47

48
    public int getNodeVersion() {
49
        return nodeVersion;
1 ✔
50
    }
51

52
    public int getEdgeVersion() {
53
        return edgeVersion;
1 ✔
54
    }
55

56
    private void handleNodeReset() {
57
        if (graph != null) {
1 ✔
58
            if (graph.getView().isMainView()) {
1 ✔
59
                GraphStore graphStore = (GraphStore) graph;
1 ✔
60
                if (graphStore.observers != null) {
1 ✔
61
                    // Same monitor used by create/destroy, so it stays safe even if auto-locking is disabled
62
                    synchronized (graphStore.observers) {
1 ✔
63
                        for (GraphObserverImpl observer : graphStore.observers) {
1 ✔
64
                            observer.resetNodeVersion();
1 ✔
65
                        }
1 ✔
66
                    }
1 ✔
67
                }
68
            } else {
1 ✔
69
                GraphViewImpl view = (GraphViewImpl) graph.getView();
1 ✔
70
                if (view.observers != null) {
1 ✔
71
                    synchronized (view.observers) {
1 ✔
72
                        for (GraphObserverImpl observer : view.observers) {
1 ✔
NEW
73
                            observer.resetNodeVersion();
×
NEW
74
                        }
×
75
                    }
1 ✔
76
                }
77
            }
78
        }
79
    }
1 ✔
80

81
    private void handleEdgeReset() {
82
        if (graph != null) {
1 ✔
83
            if (graph.getView().isMainView()) {
1 ✔
84
                GraphStore graphStore = (GraphStore) graph;
1 ✔
85
                if (graphStore.observers != null) {
1 ✔
86
                    // Same monitor used by create/destroy, so it stays safe even if auto-locking is disabled
87
                    synchronized (graphStore.observers) {
1 ✔
88
                        for (GraphObserverImpl observer : graphStore.observers) {
1 ✔
89
                            observer.resetEdgeVersion();
1 ✔
90
                        }
1 ✔
91
                    }
1 ✔
92
                }
93
            } else {
1 ✔
94
                GraphViewImpl view = (GraphViewImpl) graph.getView();
1 ✔
95
                if (view.observers != null) {
1 ✔
96
                    synchronized (view.observers) {
1 ✔
97
                        for (GraphObserverImpl observer : view.observers) {
1 ✔
NEW
98
                            observer.resetEdgeVersion();
×
NEW
99
                        }
×
100
                    }
1 ✔
101
                }
102
            }
103
        }
104
    }
1 ✔
105

106
    public int deepHashCode() {
107
        int hash = 3;
1 ✔
108
        hash = 17 * hash + this.nodeVersion;
1 ✔
109
        hash = 17 * hash + this.edgeVersion;
1 ✔
110
        return hash;
1 ✔
111
    }
112

113
    public boolean deepEquals(GraphVersion obj) {
114
        if (obj == null) {
1 ✔
115
            return false;
1 ✔
116
        }
117
        final GraphVersion other = (GraphVersion) obj;
1 ✔
118
        if (this.nodeVersion != other.nodeVersion) {
1 ✔
119
            return false;
1 ✔
120
        }
121
        if (this.edgeVersion != other.edgeVersion) {
1 ✔
122
            return false;
1 ✔
123
        }
124
        return true;
1 ✔
125
    }
126
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc