33import com .mamiyaotaru .voxelmap .VoxelConstants ;
44import java .util .concurrent .FutureTask ;
55import java .util .concurrent .LinkedBlockingQueue ;
6+ import java .util .concurrent .RejectedExecutionException ;
67import java .util .concurrent .ThreadFactory ;
78import java .util .concurrent .ThreadPoolExecutor ;
89import java .util .concurrent .TimeUnit ;
910import java .util .concurrent .atomic .AtomicInteger ;
1011import org .jetbrains .annotations .NotNull ;
1112
1213public final class ThreadManager {
14+ private static final long SAVE_FLUSH_TIMEOUT_SECONDS = 5L ;
1315 static final int concurrentThreads = Math .min (Math .max (Runtime .getRuntime ().availableProcessors () / 2 , 1 ), 4 );
1416 static final LinkedBlockingQueue <Runnable > queue = new LinkedBlockingQueue <>();
1517 public static final ThreadPoolExecutor executorService = new ThreadPoolExecutor (0 , concurrentThreads , 60L , TimeUnit .SECONDS , queue );
16- public static ThreadPoolExecutor saveExecutorService = new ThreadPoolExecutor (0 , concurrentThreads , 60L , TimeUnit .SECONDS , new LinkedBlockingQueue <>());
18+ public static ThreadPoolExecutor saveExecutorService = createSaveExecutor ();
19+ private static volatile boolean saveShutdownInProgress ;
20+ private static final AtomicInteger skippedSaveTasks = new AtomicInteger ();
1721
1822 private ThreadManager () {}
1923
@@ -28,21 +32,78 @@ public static void emptyQueue() {
2832 }
2933
3034 public static void flushSaveQueue () {
31- saveExecutorService .shutdown ();
35+ ThreadPoolExecutor executor = saveExecutorService ;
36+ saveShutdownInProgress = true ;
37+ int queuedAtStart = executor .getQueue ().size ();
38+ int activeAtStart = executor .getActiveCount ();
39+ VoxelConstants .getLogger ().info ("Flushing map save queue (queued: {}, active: {})" , queuedAtStart , activeAtStart );
3240 try {
33- while (!saveExecutorService .awaitTermination (240 , TimeUnit .SECONDS )) {
34- VoxelConstants .getLogger ().info ("Waiting for map save... (" + saveExecutorService .getQueue ().size () + ")" );
41+ executor .shutdown ();
42+ if (!executor .awaitTermination (SAVE_FLUSH_TIMEOUT_SECONDS , TimeUnit .SECONDS )) {
43+ int queuedBeforeCancel = executor .getQueue ().size ();
44+ int cancelled = executor .shutdownNow ().size ();
45+ VoxelConstants .getLogger ().warn ("Map save flush timed out after {}s; cancelling remaining saves (queued before cancel: {}, cancelled: {})" ,
46+ SAVE_FLUSH_TIMEOUT_SECONDS , queuedBeforeCancel , cancelled );
3547 }
3648 } catch (InterruptedException e ) {
3749 Thread .currentThread ().interrupt ();
50+ VoxelConstants .getLogger ().warn ("Interrupted while flushing map save queue; continuing shutdown." );
51+ } catch (RuntimeException e ) {
52+ VoxelConstants .getLogger ().warn ("Unexpected error while flushing map save queue; continuing shutdown." , e );
3853 }
39- saveExecutorService = new ThreadPoolExecutor (0 , concurrentThreads , 60L , TimeUnit .SECONDS , new LinkedBlockingQueue <>());
40- VoxelConstants .getLogger ().info ("Save queue flushed!" );
54+ int skipped = skippedSaveTasks .getAndSet (0 );
55+ VoxelConstants .getLogger ().info ("Map save flush finished (terminated: {}, skipped submissions: {})" , executor .isTerminated (), skipped );
56+ saveExecutorService = createSaveExecutor ();
57+ saveShutdownInProgress = false ;
58+ }
59+
60+ public static void submitSaveTask (Runnable task , String description ) {
61+ if (task == null ) {
62+ return ;
63+ }
64+ ThreadPoolExecutor executor = saveExecutorService ;
65+ if (saveShutdownInProgress || executor .isShutdown () || executor .isTerminated ()) {
66+ int skipped = skippedSaveTasks .incrementAndGet ();
67+ if (skipped <= 5 || VoxelConstants .DEBUG ) {
68+ VoxelConstants .getLogger ().debug ("Skipping save task during shutdown: {} (skipped count: {})" , description , skipped );
69+ }
70+ return ;
71+ }
72+
73+ Runnable guarded = () -> {
74+ try {
75+ task .run ();
76+ } catch (RuntimeException e ) {
77+ VoxelConstants .getLogger ().error ("Save task failed: {}" , description , e );
78+ } catch (Error e ) {
79+ VoxelConstants .getLogger ().error ("Severe save task error: {}" , description , e );
80+ }
81+ };
82+
83+ try {
84+ executor .execute (guarded );
85+ } catch (RejectedExecutionException e ) {
86+ int skipped = skippedSaveTasks .incrementAndGet ();
87+ if (skipped <= 5 || VoxelConstants .DEBUG ) {
88+ VoxelConstants .getLogger ().debug ("Rejected save task during shutdown: {} (skipped count: {})" , description , skipped );
89+ }
90+ } catch (RuntimeException e ) {
91+ VoxelConstants .getLogger ().warn ("Failed to submit save task: {}" , description , e );
92+ }
93+ }
94+
95+ public static boolean isSaveShutdownInProgress () {
96+ return saveShutdownInProgress ;
97+ }
98+
99+ private static ThreadPoolExecutor createSaveExecutor () {
100+ ThreadPoolExecutor executor = new ThreadPoolExecutor (0 , concurrentThreads , 60L , TimeUnit .SECONDS , new LinkedBlockingQueue <>());
101+ executor .setThreadFactory (new NamedThreadFactory ("Voxelmap WorldMap Saver Thread" ));
102+ return executor ;
41103 }
42104
43105 static {
44106 executorService .setThreadFactory (new NamedThreadFactory ("Voxelmap WorldMap Calculation Thread" ));
45- saveExecutorService .setThreadFactory (new NamedThreadFactory ("Voxelmap WorldMap Saver Thread" ));
46107 }
47108
48109 private static final class NamedThreadFactory implements ThreadFactory {
@@ -54,4 +115,4 @@ private static final class NamedThreadFactory implements ThreadFactory {
54115 @ Override
55116 public Thread newThread (@ NotNull Runnable r ) { return new Thread (r , this .name + " " + this .threadCount .getAndIncrement ()); }
56117 }
57- }
118+ }
0 commit comments