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).
109110package org .parosproxy .paros .core .scanner ;
110111
111112import java .io .IOException ;
112113import java .text .DecimalFormat ;
114+ import java .time .Instant ;
113115import java .util .ArrayList ;
114116import java .util .HashMap ;
115117import 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." );
0 commit comments