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

eXist-db / exist / 41023453632

30 Oct 2026 07:59AM UTC coverage: 58.197% (+0.006%) from 58.191%
41023453632

push

github

web-flow
Merge pull request #6952 from eXist-db/dependabot/maven/com.microsoft.onnxruntime-onnxruntime-1.31.1

Bump com.microsoft.onnxruntime:onnxruntime from 1.31.0 to 1.31.1

32504 of 61720 branches covered (52.66%)

Branch coverage included in aggregate %.

85595 of 141211 relevant lines covered (60.61%)

2.73 hits per line

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

79.25
/exist-core/src/main/java/org/exist/xquery/XQueryWatchDog.java
1
/*
2
 * eXist-db Open Source Native XML Database
3
 * Copyright (C) 2001 The eXist-db Authors
4
 *
5
 * info@exist-db.org
6
 * http://www.exist-db.org
7
 *
8
 * This library is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU Lesser General Public
10
 * License as published by the Free Software Foundation; either
11
 * version 2.1 of the License, or (at your option) any later version.
12
 *
13
 * This library is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
 * Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public
19
 * License along with this library; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21
 */
22
package org.exist.xquery;
23

24
import java.text.NumberFormat;
25
import java.util.concurrent.ScheduledFuture;
26
import java.util.concurrent.ScheduledThreadPoolExecutor;
27
import java.util.concurrent.TimeUnit;
28

29
import org.apache.logging.log4j.LogManager;
30
import org.apache.logging.log4j.Logger;
31
import org.exist.config.annotation.ConfigurationClass;
32
import org.exist.config.annotation.ConfigurationFieldAsAttribute;
33
import org.exist.dom.memtree.MemTreeBuilder;
34
import org.exist.util.Configuration;
35
import org.exist.xquery.util.ExpressionDumper;
36

37
import javax.annotation.Nullable;
38

39

40
/**
41
 * @author wolf
42
 */
43
@ConfigurationClass("watchdog")
44
public class XQueryWatchDog {
45

46
    private static final Logger LOG = LogManager.getLogger(XQueryWatchDog.class);
3 ✔
47

48
    // Shared scheduler for wall-clock kill backstop. A single daemon thread is sufficient:
49
    // kill() is non-blocking and this executor only fires lightweight Runnables.
50
    private static final ScheduledThreadPoolExecutor KILL_SCHEDULER;
51
    static {
52
        KILL_SCHEDULER = new ScheduledThreadPoolExecutor(1, r -> {
6 ✔
53
            final Thread t = new Thread(r, "exist-watchdog-kill-scheduler");
6 ✔
54
            t.setDaemon(true);
3 ✔
55
            return t;
2 ✔
56
        });
57
        KILL_SCHEDULER.setRemoveOnCancelPolicy(true);
3 ✔
58
    }
59

60
    public static final String CONFIGURATION_ELEMENT_NAME = "watchdog";
61

62
    public final static String PROPERTY_QUERY_TIMEOUT = "db-connection.watchdog.query-timeout";
63
    public final static String PROPERTY_OUTPUT_SIZE_LIMIT = "db-connection.watchdog.output-size-limit";
1 ✔
64

65
    private final XQueryContext context;
66

67
    @ConfigurationFieldAsAttribute("query-timeout")
68
    private long timeout = Long.MAX_VALUE;
3 ✔
69

70
    @ConfigurationFieldAsAttribute("output-size-limit")
71
    private int maxNodesLimit = Integer.MAX_VALUE;
3 ✔
72

73
    private long startTime;
74

75
    // volatile: kill() is called from a different thread than proceed(); without visibility
76
    // guarantee the executing thread may never observe terminate=true (JMM data race).
77
    private volatile boolean terminate = false;
3 ✔
78

79
    // Set alongside terminate when the kill was triggered by a timeout (not a client cancel),
80
    // allowing callers to send "error" rather than "cancelled" in the response.
81
    private volatile boolean timedOut = false;
3 ✔
82

83
    // Scheduled future for wall-clock kill; cancelled when the watchdog resets or fires timeout via proceed().
84
    private volatile ScheduledFuture<?> scheduledKill;
85

86
    private String runningThread = null;
3 ✔
87

88
    public XQueryWatchDog(XQueryContext context) {
2 ✔
89
        this.context = context;
3 ✔
90
        configureDefaults();
2 ✔
91
        reset();
2 ✔
92
    }
1 ✔
93

94
    /**
95
     * Track the name of the thread currently running this query.
96
     * Used for JMX stats.
97
     *
98
     * @param name name of the thread
99
     */
100
    public void setRunningThread(String name) {
101
        this.runningThread = name;
3 ✔
102
    }
1 ✔
103

104
    /**
105
     * Get the name of last thread which has been running this query.
106
     *
107
     * @return name of the last thread
108
     */
109
    public String getRunningThread() {
110
        return runningThread;
3 ✔
111
    }
112

113
    private void configureDefaults() {
114
        @Nullable final Configuration conf = context.getConfiguration();
4 ✔
115
        if (conf != null) {
2 ✔
116
            Object option = conf.getProperty(PROPERTY_QUERY_TIMEOUT);
4 ✔
117
            if (option != null) {
2 ✔
118
                timeout = (Long) option;
5 ✔
119
            }
120
            if (timeout <= 0) {
5 ✔
121
                timeout = Long.MAX_VALUE;
3 ✔
122
            }
123

124
            option = conf.getProperty(PROPERTY_OUTPUT_SIZE_LIMIT);
4 ✔
125
            if (option != null) {
2 ✔
126
                maxNodesLimit = (Integer) option;
5 ✔
127
            }
128
        }
129
    }
1 ✔
130

131
    public void setTimeoutFromOption(Option option) throws XPathException {
132
            final String[] contents = option.tokenizeContents();
3 ✔
133
            if(contents.length != 1)
4 ✔
134
                    {throw new XPathException((Expression) null, "Option 'timeout' should have exactly one parameter: the timeout value.");}
6 ✔
135
                long time;
136
                try {
137
                        time = Long.parseLong(contents[0]);
5 ✔
138
                } catch (final NumberFormatException e) {
2 ✔
139
                        throw new XPathException((Expression) null, "Error parsing timeout value in option " + option.getQName().getStringValue());
9 ✔
140
                }
141
                if (time <= 0) {
4 ✔
142
                        time = Long.MAX_VALUE;
2 ✔
143
                }
144
                setTimeout(time);
3 ✔
145
                if (LOG.isDebugEnabled()) {
3 !
146
                        final NumberFormat nf = NumberFormat.getNumberInstance();
×
147
            LOG.debug("timeout set from option: {} ms.", nf.format(timeout));
×
148
                }
149
    }
1 ✔
150

151
    public void setTimeout(long time) {
152
        timeout = time;
3 ✔
153
        cancelScheduledKill();
2 ✔
154
        if (time > 0 && time != Long.MAX_VALUE) {
8 !
155
            scheduledKill = KILL_SCHEDULER.schedule(() -> killAsTimeout(0), time, TimeUnit.MILLISECONDS);
12 ✔
156
        }
157
    }
1 ✔
158

159
    public void setMaxNodes(int maxNodes) {
160
            maxNodesLimit = maxNodes;
3 ✔
161
    }
1 ✔
162
    
163
    public void setMaxNodesFromOption(Option option) throws XPathException {
164
            final String[] contents = option.tokenizeContents();
3 ✔
165
            if(contents.length != 1)
4 !
166
                    {throw new XPathException((Expression) null, "Option 'output-size-limit' should have exactly one parameter: the output-size-limit value.");}
×
167
                try {
168
                        setMaxNodes(Integer.parseInt(contents[0]));
6 ✔
169
                } catch (final NumberFormatException e) {
1 ✔
170
                        throw new XPathException((Expression) null, "Error parsing output-size-limit value in option " + option.getQName().getStringValue());
×
171
                }
172
                if (LOG.isDebugEnabled()) {
3 !
173
                        final NumberFormat nf = NumberFormat.getNumberInstance();
×
174
            LOG.debug("output-size-limit set from option: {}", nf.format(maxNodesLimit));
×
175
                }
176
    }
1 ✔
177
    
178
    public void proceed(Expression expr) throws TerminatedException {
179
            if(terminate) {
3 ✔
180
                    if(expr == null)
2 !
181
                            {expr = context.getRootExpression();}
×
182
                    cleanUp();
2 ✔
183
                    // The scheduler may race ahead and set terminate before proceed() checks elapsed time.
184
                    // Re-check elapsed here so that a scheduler-triggered kill emits TimeoutException,
185
                    // not the generic "killed by server" message that belongs to explicit client cancels.
186
                    if (timeout != Long.MAX_VALUE && System.currentTimeMillis() - startTime > timeout) {
13 ✔
187
                        final NumberFormat nf = NumberFormat.getNumberInstance();
2 ✔
188
                        LOG.warn("Query exceeded predefined timeout ({} ms.): {}", nf.format(System.currentTimeMillis() - startTime), ExpressionDumper.dump(expr));
11 ✔
189
                        throw new TerminatedException.TimeoutException(expr.getLine(), expr.getColumn(),
8 ✔
190
                                "The query exceeded the predefined timeout and has been killed.");
1 ✔
191
                    }
192
                    throw new TerminatedException(expr.getLine(), expr.getColumn(),
8 ✔
193
                                    "The query has been killed by the server.");
1 ✔
194
            }
195
        if (timeout != Long.MAX_VALUE) {
5 ✔
196
            final long elapsed = System.currentTimeMillis() - startTime;
5 ✔
197
            if(elapsed > timeout) {
5 !
198
                if(expr == null)
×
199
                    {expr = context.getRootExpression();}
×
200
                final NumberFormat nf = NumberFormat.getNumberInstance();
×
201
                LOG.warn("Query exceeded predefined timeout ({} ms.): {}", nf.format(elapsed), ExpressionDumper.dump(expr));
×
202
                cleanUp();
×
203
                throw new TerminatedException.TimeoutException(expr.getLine(), expr.getColumn(),
×
204
                        "The query exceeded the predefined timeout and has been killed.");
×
205
            }
206
        }
207
    }
1 ✔
208
    
209
    public void proceed(Expression expr, MemTreeBuilder builder) throws TerminatedException {
210
        proceed(expr);
3 ✔
211
        if(maxNodesLimit > 0 && builder.getSize() > maxNodesLimit) {
8 !
212
            if(expr == null)
×
213
                {expr = context.getRootExpression();}
×
214
            final NumberFormat nf = NumberFormat.getNumberInstance();
×
215
            LOG.warn("Query exceeded predefined output-size-limit ({}) for document fragments: {}", nf.format(maxNodesLimit), ExpressionDumper.dump(expr));
×
216
            cleanUp();
×
217
            throw new TerminatedException.SizeLimitException(expr.getLine(), expr.getColumn(),
×
218
                    "The constructed document fragment exceeded the predefined output-size-limit (current: " +
×
219
                    nf.format(builder.getSize()) + "; allowed: " + nf.format(maxNodesLimit) +
×
220
                    "). The query has been killed.");
221
        }
222
    }
1 ✔
223
    
224
    private void cancelScheduledKill() {
225
        final ScheduledFuture<?> f = scheduledKill;
3 ✔
226
        if (f != null) {
2 ✔
227
            f.cancel(false);
4 ✔
228
            scheduledKill = null;
3 ✔
229
        }
230
    }
1 ✔
231

232
    public void cleanUp() {
233
        cancelScheduledKill();
2 ✔
234
    }
1 ✔
235
    
236
    public void kill(long waitTime) {
237
            terminate = true;
3 ✔
238
    }
1 ✔
239

240
    /** Kill the query and mark it as timed out (not cancelled by client). */
241
    public void killAsTimeout(long waitTime) {
242
        timedOut = true;
3 ✔
243
        terminate = true;
3 ✔
244
    }
1 ✔
245

246
    /** True if the query was killed because it exceeded its time limit (not cancelled by client). */
247
    public boolean isTimedOut() {
248
        return timedOut;
3 ✔
249
    }
250
    
251
    public XQueryContext getContext() {
252
            return context;
3 ✔
253
    }
254
         
255
         public long getStartTime() {
256
                 return startTime;
3 ✔
257
         }
258
    
259
    public void reset() {
260
        cancelScheduledKill();
2 ✔
261
        startTime = System.currentTimeMillis();
3 ✔
262
        terminate = false;
3 ✔
263
        timedOut = false;
3 ✔
264
    }
1 ✔
265
    
266
    public boolean isTerminating()
267
    {
268
            return( terminate );
3 ✔
269
    }
270
}
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