Skip to content

Commit a3b125a

Browse files
committed
ascan: Pause timers when pausing scan
Signed-off-by: kingthorin <kingthorin@users.noreply.github.com>
1 parent 602922f commit a3b125a

File tree

9 files changed

+842
-52
lines changed

9 files changed

+842
-52
lines changed

zap/src/main/java/org/parosproxy/paros/core/scanner/AbstractPlugin.java

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,15 @@
7979
// ZAP: 2023/07/06 Deprecate delayInMs.
8080
// ZAP: 2024/10/16 Remove isFileExist call from isSuccess and isPage200 - it results in too many
8181
// FPs.
82+
// ZAP: 2025/12/08 Pause timers when pausing the scan (Issue 3455).
8283
package org.parosproxy.paros.core.scanner;
8384

8485
import java.io.IOException;
8586
import java.io.UnsupportedEncodingException;
8687
import java.net.URLDecoder;
8788
import java.net.URLEncoder;
8889
import java.security.InvalidParameterException;
90+
import java.time.Instant;
8991
import java.util.Date;
9092
import java.util.Map;
9193
import java.util.Objects;
@@ -398,15 +400,15 @@ public void run() {
398400

399401
try {
400402
if (!isStop()) {
401-
this.started = new Date();
403+
setTimeStarted();
402404
scan();
403405
}
404406

405407
} catch (Exception e) {
406408
getLog().error(e.getMessage(), e);
407409
} finally {
410+
setTimeFinished();
408411
notifyPluginCompleted(getParent());
409-
this.finished = new Date();
410412
}
411413
}
412414

@@ -844,6 +846,23 @@ protected boolean isStop() {
844846
return parent.isStop() || parent.isSkipped(this);
845847
}
846848

849+
/** Helper to block efficiently while the parent scanner is paused. */
850+
public void handlePause() {
851+
if (parent == null) {
852+
return;
853+
}
854+
855+
// Parent is a HostProcess; HostProcess delegates wait/isPaused to the Scanner.
856+
if (isStop()) {
857+
parent.stop();
858+
return;
859+
}
860+
parent.waitIfPaused();
861+
if (isStop()) {
862+
return;
863+
}
864+
}
865+
847866
@Override
848867
public boolean isEnabled() {
849868
return enabled;
@@ -1353,13 +1372,26 @@ public Date getTimeFinished() {
13531372

13541373
@Override
13551374
public void setTimeStarted() {
1356-
this.started = new Date();
1375+
Instant now = getNowInstant();
1376+
this.started = Date.from(now);
13571377
this.finished = null;
13581378
}
13591379

13601380
@Override
13611381
public void setTimeFinished() {
1362-
this.finished = new Date();
1382+
Instant now = getNowInstant();
1383+
this.finished = Date.from(now);
1384+
}
1385+
1386+
private Instant getNowInstant() {
1387+
Instant now = null;
1388+
if (parent != null) {
1389+
now = parent.getEffectiveInstant();
1390+
}
1391+
if (now == null) {
1392+
now = Instant.now();
1393+
}
1394+
return now;
13631395
}
13641396

13651397
@Override

zap/src/main/java/org/parosproxy/paros/core/scanner/HostProcess.java

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,12 @@
106106
// ZAP: 2022/09/21 Use format specifiers instead of concatenation when logging.
107107
// ZAP: 2023/01/10 Tidy up logger.
108108
// ZAP: 2023/05/17 Skip rules that reach the maximum number of alerts.
109+
// ZAP: 2025/12/08 Pause timers when pausing the scan (Issue 3455).
109110
package org.parosproxy.paros.core.scanner;
110111

111112
import java.io.IOException;
112113
import java.text.DecimalFormat;
114+
import java.time.Instant;
113115
import java.util.ArrayList;
114116
import java.util.HashMap;
115117
import java.util.HashSet;
@@ -174,7 +176,7 @@ public class HostProcess implements Runnable {
174176
*/
175177
private final Map<Integer, PluginStats> mapPluginStats = new HashMap<>();
176178

177-
private long hostProcessStartTime = 0;
179+
private Instant hostProcessStartInstant = null;
178180

179181
// ZAP: progress related
180182
private int nodeInScopeCount = 0;
@@ -342,8 +344,7 @@ public void run() {
342344
LOGGER.debug("HostProcess.run");
343345

344346
try {
345-
hostProcessStartTime = System.currentTimeMillis();
346-
347+
hostProcessStartInstant = getNowInstant();
347348
// Initialise plugin factory to report the state of the plugins ASAP.
348349
pluginFactory.reset();
349350
synchronized (mapPluginStats) {
@@ -480,7 +481,8 @@ private void logScanInfo() {
480481
}
481482

482483
private void processPlugin(final Plugin plugin) {
483-
mapPluginStats.get(plugin.getId()).start();
484+
PluginStats ps = mapPluginStats.get(plugin.getId());
485+
ps.start(getNowInstant());
484486

485487
if (nodeInScopeCount == 0) {
486488
pluginSkipped(
@@ -532,6 +534,12 @@ private void processPlugin(final Plugin plugin) {
532534
}
533535
}
534536

537+
private Instant getNowInstant() {
538+
return (parentScanner != null && parentScanner.getEffectiveInstant() != null)
539+
? parentScanner.getEffectiveInstant()
540+
: Instant.now();
541+
}
542+
535543
private void traverse(StructuralNode node, boolean incRelatedSiblings, TraverseAction action) {
536544
if (node == null || isStop()) {
537545
return;
@@ -680,8 +688,10 @@ private boolean scanMessage(Plugin plugin, int messageId) {
680688
if (this.isStop()) {
681689
return false;
682690
}
691+
checkPause();
683692
thread = threadPool.getFreeThreadAndRun(test);
684693
if (thread == null) {
694+
checkPause();
685695
Util.sleep(200);
686696
}
687697

@@ -795,11 +805,16 @@ public HttpSender getHttpSender() {
795805
*/
796806
public boolean isStop() {
797807
if (this.scannerParam.getMaxScanDurationInMins() > 0) {
798-
if (System.currentTimeMillis() - this.hostProcessStartTime
799-
> TimeUnit.MINUTES.toMillis(this.scannerParam.getMaxScanDurationInMins())) {
800-
this.stopReason =
801-
Constant.messages.getString("ascan.progress.label.skipped.reason.maxScan");
802-
this.stop();
808+
if (hostProcessStartInstant != null) {
809+
long elapsedMs =
810+
getNowInstant().toEpochMilli() - hostProcessStartInstant.toEpochMilli();
811+
if (elapsedMs
812+
> TimeUnit.MINUTES.toMillis(this.scannerParam.getMaxScanDurationInMins())) {
813+
this.stopReason =
814+
Constant.messages.getString(
815+
"ascan.progress.label.skipped.reason.maxScan");
816+
this.stop();
817+
}
803818
}
804819
}
805820
return (isStop || parentScanner.isStop());
@@ -811,15 +826,32 @@ public boolean isStop() {
811826
* @return true if the process has been paused
812827
*/
813828
public boolean isPaused() {
814-
return parentScanner.isPaused();
829+
return parentScanner != null && parentScanner.isPaused();
815830
}
816831

817832
private void checkPause() {
818-
while (parentScanner.isPaused() && !isStop()) {
819-
Util.sleep(500);
833+
if (parentScanner != null) {
834+
parentScanner.waitIfPaused();
820835
}
821836
}
822837

838+
/**
839+
* Delegates to the parent Scanner to block while the global scan is paused. Provides the same
840+
* API that plugins/host code expect to call on their parent.
841+
*/
842+
public void waitIfPaused() {
843+
if (parentScanner != null) {
844+
parentScanner.waitIfPaused();
845+
}
846+
}
847+
848+
public Instant getEffectiveInstant() {
849+
if (parentScanner != null) {
850+
return parentScanner.getEffectiveInstant();
851+
}
852+
return Instant.now();
853+
}
854+
823855
public int getPercentageComplete() {
824856
return this.percentage;
825857
}
@@ -854,7 +886,10 @@ private void notifyHostProgress(String msg) {
854886
}
855887

856888
private void notifyHostComplete() {
857-
long diffTimeMillis = System.currentTimeMillis() - hostProcessStartTime;
889+
long diffTimeMillis =
890+
(hostProcessStartInstant != null)
891+
? getNowInstant().toEpochMilli() - hostProcessStartInstant.toEpochMilli()
892+
: 0L;
858893
String diffTimeString = decimalFormat.format(diffTimeMillis / 1000.0) + "s";
859894
LOGGER.info(
860895
"completed host {} in {} with {} alert(s) raised.",
@@ -1099,9 +1134,11 @@ public boolean isSkipped(Plugin plugin) {
10991134

11001135
} else if (this.scannerParam.getMaxRuleDurationInMins() > 0
11011136
&& plugin.getTimeStarted() != null) {
1102-
long endtime = System.currentTimeMillis();
1137+
long endtime;
11031138
if (plugin.getTimeFinished() != null) {
11041139
endtime = plugin.getTimeFinished().getTime();
1140+
} else {
1141+
endtime = getNowInstant().toEpochMilli();
11051142
}
11061143
if (endtime - plugin.getTimeStarted().getTime()
11071144
> TimeUnit.MINUTES.toMillis(this.scannerParam.getMaxRuleDurationInMins())) {
@@ -1142,7 +1179,8 @@ void pluginCompleted(Plugin plugin) {
11421179
// Plugin was not processed
11431180
return;
11441181
}
1145-
pluginStats.stopped();
1182+
// Stop plugin stats with scanner-aligned Instant so totalTime excludes paused time.
1183+
pluginStats.stopped(getNowInstant());
11461184

11471185
StringBuilder sb = new StringBuilder();
11481186
if (isStop()) {
@@ -1160,7 +1198,9 @@ void pluginCompleted(Plugin plugin) {
11601198
}
11611199

11621200
sb.append(hostAndPort).append(" | ").append(plugin.getCodeName());
1163-
String diffTimeString = decimalFormat.format(pluginStats.getTotalTime() / 1000.0);
1201+
// Use scanner-aligned Instant when reporting total time for running/completed plugin.
1202+
long totalMs = pluginStats.getTotalTimeMillis(getNowInstant());
1203+
String diffTimeString = decimalFormat.format(totalMs / 1000.0);
11641204
sb.append(" in ").append(diffTimeString).append('s');
11651205
sb.append(" with ").append(pluginStats.getMessageCount()).append(" message(s) sent");
11661206
sb.append(" and ").append(pluginStats.getAlertCount()).append(" alert(s) raised.");

zap/src/main/java/org/parosproxy/paros/core/scanner/PluginStats.java

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
*/
2020
package org.parosproxy.paros.core.scanner;
2121

22+
import java.time.Instant;
2223
import org.zaproxy.zap.utils.Stats;
2324

2425
/**
@@ -101,14 +102,39 @@ public String getSkippedReason() {
101102
return skippedReason;
102103
}
103104

104-
/** Starts the plugin stats. */
105+
/** Starts the plugin stats (wall-clock fallback). */
105106
void start() {
106-
startTime = System.currentTimeMillis();
107+
start(null);
108+
}
109+
110+
/**
111+
* Starts the plugin stats using a scanner-aligned Instant.
112+
*
113+
* <p>Use this when the caller has an Instant from Scanner.getEffectiveInstant() to ensure
114+
* paused time is excluded.
115+
*/
116+
void start(Instant now) {
117+
startTime = getNowMilliseconds(now);
107118
Stats.incCounter(Scanner.ASCAN_RULE_PREFIX + this.pluginId + Scanner.STARTED_POSTFIX);
108119
}
109120

121+
private long getNowMilliseconds(Instant now) {
122+
return (now != null) ? now.toEpochMilli() : System.currentTimeMillis();
123+
}
124+
125+
/** Stops the plugin stats (wall-clock fallback). */
110126
void stopped() {
111-
totalTime = System.currentTimeMillis() - startTime;
127+
stopped(null);
128+
}
129+
130+
/**
131+
* Stops the plugin stats using a scanner-aligned Instant.
132+
*
133+
* <p>Use this when the caller has an Instant from Scanner.getEffectiveInstant() to ensure
134+
* paused time is excluded.
135+
*/
136+
void stopped(Instant now) {
137+
totalTime = getNowMilliseconds(now) - startTime;
112138
Stats.incCounter(
113139
Scanner.ASCAN_RULE_PREFIX + this.pluginId + Scanner.TIME_POSTFIX, totalTime);
114140
}
@@ -123,11 +149,29 @@ public long getStartTime() {
123149
return startTime;
124150
}
125151

126-
public long getTotalTime() {
127-
if (totalTime == 0 && startTime > 0) {
128-
return System.currentTimeMillis() - startTime;
152+
/**
153+
* Returns the total elapsed time in milliseconds for this plugin.
154+
*
155+
* <p>If the plugin has finished, returns (finished - started). If the plugin is still running
156+
* and {@code now} is provided, returns (now - started). If the plugin is still running and
157+
* {@code now} is null, uses Instant.now() as fallback.
158+
*
159+
* <p>Callers should pass the scanner-aligned Instant (Scanner.getEffectiveInstant()) when
160+
* available so paused time is excluded.
161+
*/
162+
public long getTotalTimeMillis(Instant now) {
163+
if (startTime <= 0) {
164+
return 0L;
165+
}
166+
if (totalTime != 0L) {
167+
return totalTime;
129168
}
130-
return totalTime;
169+
return getNowMilliseconds(now) - startTime;
170+
}
171+
172+
/** Backwards-compatible API: returns total milliseconds using wall-clock fallback. */
173+
public long getTotalTime() {
174+
return getTotalTimeMillis(null);
131175
}
132176

133177
/**

0 commit comments

Comments
 (0)