/*
 * Microsoft JDBC Driver for SQL Server Copyright(c) Microsoft Corporation All rights reserved. This program is made
 * available under the terms of the MIT License. See the LICENSE file in the project root for more information.
 */
package com.microsoft.sqlserver.jdbc;

import java.io.Serializable;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;


class SharedTimer implements Serializable {
    /**
     * Always update serialVersionUID when prompted
     */
    private static final long serialVersionUID = -4069361613863955760L;
    
    static final String CORE_THREAD_PREFIX = "mssql-jdbc-shared-timer-core-";
    private static final AtomicLong CORE_THREAD_COUNTER = new AtomicLong();
    private static SharedTimer instance;

    /**
     * Unique ID of this SharedTimer
     */
    private final long id = CORE_THREAD_COUNTER.getAndIncrement();
    /**
     * Number of outstanding references to this SharedTimer
     */
    private int refCount = 0;
    private ScheduledThreadPoolExecutor executor;

    private SharedTimer() {
        executor = new ScheduledThreadPoolExecutor(1, task -> {
            Thread t = new Thread(task, CORE_THREAD_PREFIX + id);
            t.setDaemon(true);
            return t;
        });
        executor.setRemoveOnCancelPolicy(true);
    }

    public long getId() {
        return id;
    }

    /**
     * @return Whether there is an instance of the SharedTimer currently allocated.
     */
    static synchronized boolean isRunning() {
        return instance != null;
    }

    /**
     * Remove a reference to this SharedTimer.
     *
     * If the reference count reaches zero then the underlying executor will be shutdown so that its thread stops.
     */
    public synchronized void removeRef() {
        if (refCount <= 0) {
            throw new IllegalStateException("removeRef() called more than actual references");
        }
        refCount -= 1;
        if (refCount == 0) {
            // Removed last reference so perform cleanup
            executor.shutdownNow();
            executor = null;
            instance = null;
        }
    }

    /**
     * Retrieve a reference to existing SharedTimer or create a new one.
     *
     * The SharedTimer's reference count will be incremented to account for the new reference.
     *
     * When the caller is finished with the SharedTimer it must be released via {@link#removeRef}
     */
    public static synchronized SharedTimer getTimer() {
        if (instance == null) {
            // No shared object exists so create a new one
            instance = new SharedTimer();
        }
        instance.refCount += 1;
        return instance;
    }

    /**
     * Schedule a task to execute in the future using this SharedTimer's internal executor.
     */
    public ScheduledFuture<?> schedule(TDSTimeoutTask task, long delaySeconds) {
        return schedule(task, delaySeconds, TimeUnit.SECONDS);
    }

    /**
     * Schedule a task to execute in the future using this SharedTimer's internal executor.
     */
    public ScheduledFuture<?> schedule(TDSTimeoutTask task, long delay, TimeUnit unit) {
        if (executor == null) {
            throw new IllegalStateException("Cannot schedule tasks after shutdown");
        }
        return executor.schedule(task, delay, unit);
    }
}

@@ -437,16 +437,66 @@
 meUnit;%0A
+import java.util.concurrent.atomic.AtomicInteger;%0A
 import j
@@ -720,20 +720,16 @@
 55760L;%0A
-    
 %0A    sta
@@ -799,16 +799,17 @@
 core-%22;%0A
+%0A
     priv
@@ -899,30 +899,41 @@
 tic 
-SharedTimer instance;%0A
+final Object lock = new Object();
 %0A   
@@ -1141,24 +1141,109 @@
 ate 
+f
 in
-t refCount = 0
+al AtomicInteger refCount = new AtomicInteger();%0A%0A    private static volatile SharedTimer instance
 ;%0A  
@@ -1732,29 +1732,16 @@
  static 
-synchronized 
 boolean 
@@ -1990,29 +1990,16 @@
  public 
-synchronized 
 void rem
@@ -2005,24 +2005,58 @@
 moveRef() %7B%0A
+        synchronized (lock) %7B%0A    
         if (
@@ -2063,16 +2063,22 @@
 refCount
+.get()
  %3C= 0) %7B
@@ -2082,32 +2082,36 @@
 ) %7B%0A            
+   
+ 
 throw new Illega
@@ -2189,33 +2189,18 @@
     
-%7D%0A
     
+%7D%0A
     
-refCount -= 1;%0A
     
@@ -2215,16 +2215,34 @@
 refCount
+.decrementAndGet()
  == 0) %7B
@@ -2238,24 +2238,28 @@
 t() == 0) %7B%0A
+    
             
@@ -2307,32 +2307,36 @@
 nup%0A            
+    
 executor.shutdow
@@ -2347,32 +2347,36 @@
 ();%0A            
+   
+ 
 executor = null;
@@ -2368,32 +2368,36 @@
 xecutor = null;%0A
+    
             inst
@@ -2401,32 +2401,46 @@
 nstance = null;%0A
+            %7D%0A
         %7D%0A    %7D%0A
@@ -2754,29 +2754,16 @@
  static 
-synchronized 
 SharedTi
@@ -2771,32 +2771,66 @@
 er getTimer() %7B%0A
+        synchronized (lock) %7B%0A    
         if (inst
@@ -2857,16 +2857,20 @@
         
+   
+ 
 // No sh
@@ -2912,32 +2912,36 @@
 one%0A            
+    
 instance = new S
@@ -2954,34 +2954,42 @@
 imer();%0A        
-%7D%0A
+    %7D%0A    
         instance
@@ -3001,23 +3001,38 @@
 ount
- += 1;%0A
+.getAndIncrement();%0A  
         
 retu
@@ -3019,32 +3019,34 @@
 nt();%0A          
+  
 return instance;
@@ -3042,24 +3042,34 @@
 n instance;%0A
+        %7D%0A
     %7D%0A%0A    /

Fix | Fix SharedTimer implementation to use class level lock for thread safety (#1046)