All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.redhat.lightblue.migrator.ThreadMonitor Maven / Gradle / Ivy

There is a newer version: 2.56.1
Show newest version
package com.redhat.lightblue.migrator;

import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ThreadMonitor extends Thread {

    private static final Logger LOGGER = LoggerFactory.getLogger(ThreadMonitor.class);

    private long threadTimeout = 10l * 60l * 1000l; // 10 minutes timeout

    private long wakeupInterval = 60l * 1000l; // Wake up every minute

    private final List statusListeners = new ArrayList<>();

    public enum Status {
        alive, killed, abandoned
    };

    private class ThreadStatus {
        final MonitoredThread thread;

        long lastPing = System.currentTimeMillis();
        String lastPingMsg;
        long killTime = 0;
        Status status;

        public ThreadStatus(MonitoredThread t) {
            this.thread = t;
            this.status = Status.alive;
        }

        public void kill() {
            status = Status.killed;
            killTime = System.currentTimeMillis();
            ((Thread) thread).interrupt();
            threadStatusChanged(thread);
        }

        /**
         * Returns true if thread was alive before, and now it timed out
         */
        public boolean timeout() {
            if (status == Status.alive) {
                if (System.currentTimeMillis() - lastPing > threadTimeout) {
                    kill();
                    return true;
                }
            }
            return false;
        }

        /**
         * Returns true if thread is killed before and now it is abandoned
         */
        public boolean abandon() {
            if (status == Status.killed && killTime > 0) {
                if (System.currentTimeMillis() - killTime > threadTimeout) {
                    status = Status.abandoned;
                    ((Thread) thread).interrupt();
                    threadStatusChanged(thread);
                    return true;
                }
            }
            return false;
        }

        @Override
        public String toString() {
            return "Thread=" + thread + ", lastPing:" + (new Date(lastPing).toString()) + ", status=" + status
                    + " msg=" + lastPingMsg + (killTime > 0 ? ", killlTime:" + (new Date(killTime)) : "");
        }

    }

    private final Map threadMap = new HashMap<>();

    public ThreadMonitor(long threadTimeout) {
        this.threadTimeout = threadTimeout;
    }

    public ThreadMonitor() {
    }

    /**
     * Registers a thread status listener
     */
    public synchronized void registerThreadStatusListener(ThreadStatusListener listener) {
        statusListeners.add(listener);
    }

    /**
     * Unregisters a thread status listener
     */
    public synchronized void unregisterThreadStatusListener(ThreadStatusListener listener) {
        statusListeners.remove(listener);
    }

    protected void threadStatusChanged(MonitoredThread thread) {
        ArrayList l;
        synchronized (this) {
            l = new ArrayList<>(statusListeners);
        }
        for (ThreadStatusListener x : l) {
            x.threadStatusChanged(thread, this);
        }
    }

    public long getThreadTimeout() {
        return threadTimeout;
    }

    public void ping(String msg) {
        Thread currentThread = Thread.currentThread();
        LOGGER.debug("Ping from {}: {}", currentThread, msg);
        if (currentThread instanceof MonitoredThread) {
            MonitoredThread thread = (MonitoredThread) currentThread;
            ThreadStatus status = threadMap.get(thread);
            if (status == null) {
                synchronized (this) {
                    if (!threadMap.containsKey(thread)) {
                        threadMap.put(thread, status = new ThreadStatus(thread));
                    } else {
                        status = threadMap.get(thread);
                    }
                }
            }
            status.lastPingMsg = msg;
            LOGGER.debug("Status:{}", status);
            long now = System.currentTimeMillis();
            if (status.status == Status.killed) {
                currentThread.interrupt();
                throw new RuntimeException("Thread is killed:" + currentThread);
            }
            synchronized (status) {
                if (status.timeout() || status.abandon()) {
                    throw new RuntimeException("Thread timed out:" + currentThread);
                }
                status.lastPing = now;
            }
        }
    }

    public void endThread() {
        Thread currentThread = Thread.currentThread();
        LOGGER.debug("End thread {}", currentThread);
        if (currentThread instanceof MonitoredThread) {
            MonitoredThread t = (MonitoredThread) currentThread;
            synchronized (this) {
                threadMap.remove(t);
            }
        }
    }

    private synchronized void reap(Map threads) {
        List dead = new ArrayList<>();
        for (MonitoredThread t : threads.keySet()) {
            if (!((Thread) t).isAlive()) {
                dead.add(t);
            }
        }
        for (MonitoredThread t : dead) {
            threads.remove(t);
        }
    }

    public synchronized Status getStatus(MonitoredThread t) {
        ThreadStatus s = threadMap.get(t);
        if (s != null) {
            return s.status;
        } else {
            return null;
        }
    }

    /**
     * Returns threads in an array. Some elements may be null.
     */
    private static Thread[] getThreads(ThreadGroup g) {
        int mul = 1;
        do {
            Thread[] arr = new Thread[g.activeCount() * mul + 1];
            if (g.enumerate(arr) < arr.length) {
                return arr;
            }
            mul++;
        } while (true);
    }

    /**
     * Return the count of threads that are in any of the statuses
     */
    public int getThreadCount(ThreadGroup group, Status... s) {
        Thread[] threads = getThreads(group);
        int count = 0;
        for (Thread t : threads) {
            if (t instanceof MonitoredThread) {
                Status status = getStatus((MonitoredThread) t);
                if (status != null) {
                    for (Status x : s) {
                        if (x == status) {
                            count++;
                        }
                    }
                }
            }
        }
        return count;
    }

    public int getThreadCount(Status... s) {
        Map snapshot;
        synchronized (this) {
            snapshot = new HashMap<>(threadMap);
        }
        int count = 0;
        for (ThreadStatus ts : snapshot.values()) {
            for (Status x : s) {
                if (x == ts.status) {
                    count++;
                }
            }
        }
        return count;
    }

    private final Object waiter = new Object();
    private boolean runNow = false;

    public void runNow() {
        synchronized (waiter) {
            waiter.notifyAll();
            runNow = true;
        }
    }

    @Override
    public void run() {
        LOGGER.debug("Starting with thread timeout:{}", threadTimeout);
        // Wake up every minute, look at all registered threads
        // No exceptions can be thrown from this code
        int abandonedThreadCount = 0;
        while (true) {
            // Wait for a minute
            synchronized (waiter) {
                if (runNow) {
                    runNow = false;
                } else {
                    try {
                        waiter.wait(wakeupInterval);
                    } catch (Exception e) {
                    }
                }
            }
            LOGGER.debug("Running thread monitor after {} msec", threadTimeout);
            Breakpoint.checkpoint("ThreadMonitor:check");
            // Take a snapshot
            Map snapshot;
            synchronized (this) {
                reap(threadMap);
                snapshot = new HashMap<>(threadMap);
            }
            LOGGER.debug("Thread snapshot:{}", snapshot);
            long now = System.currentTimeMillis();
            // Check threads
            List unresponsive = new ArrayList<>();
            for (Map.Entry entry : snapshot.entrySet()) {
                MonitoredThread thread = entry.getKey();
                ThreadStatus status = entry.getValue();
                if (status.timeout()) {
                    unresponsive.add(status);
                    LOGGER.error("Thread timed out, thread:{}", status);
                } else if (status.abandon()) {
                    unresponsive.add(status);
                    LOGGER.error("Thread abandoned, thread:{}", status);
                }
            }
            if (!unresponsive.isEmpty()) {
                for (ThreadStatus x : unresponsive) {
                    LOGGER.error("Unresponsive thread:{}", x);
                }
            }
            abandonedThreadCount++;
            if (abandonedThreadCount > 10) {
                abandonedThreadCount = 0;
                int n = 0;
                for (Map.Entry entry : snapshot.entrySet()) {
                    if (entry.getValue().status == Status.abandoned) {
                        n++;
                    }
                }
                if (n > 0) {
                    LOGGER.error("ALERT: There are {} abandoned threads", n);
                }
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy