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

com.mindoo.domino.jna.gc.NotesGC Maven / Gradle / Ivy

There is a newer version: 0.9.53
Show newest version
package com.mindoo.domino.jna.gc;

import java.io.PrintWriter;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Callable;

import com.mindoo.domino.jna.errors.NotesError;
import com.mindoo.domino.jna.internal.NotesNativeAPI;
import com.mindoo.domino.jna.utils.PlatformUtils;

/**
 * Utility class to simplify memory management with Notes handles. The class tracks
 * handle creation and disposal.
* By using {@link #runWithAutoGC(Callable)}, the * collected handles are automatically disposed when code execution is done.
*
* An alternative approach is to use a try-with-resources block on the {@link DominoGCContext} * returned by {@link #initThread()}, e.g.
*
* * try (DominoGCContext ctx = initThread()) {
*    // use Domino JNA classes, e.g.
*    NotesDatabase db = new NotesDatabase("", "names.nsf", "");
*
* } catch (Exception e) {
*    log(Level.SEVERE, "Error accessing Domino data", e);
* }
*
* * @author Karsten Lehmann */ public class NotesGC { private static ThreadLocal threadContext = new ThreadLocal<>(); /** * Returns the GC context for the current thread * * @return context * @throws IllegalStateException if no context is active */ private static DominoGCContext getThreadContext() { DominoGCContext ctx = threadContext.get(); if (ctx==null) { throw new IllegalStateException("Thread is not enabled for auto GC. Either run your code via NotesGC.runWithAutoGC(Callable) or via try-with-resources on the object returned by NotesGC.initThread()."); } return ctx; } /** * Method to enable GC debug logging for the active thread's {@link DominoGCContext} * * @param enabled true if enabled */ public static void setDebugLoggingEnabled(boolean enabled) { DominoGCContext ctx = threadContext.get(); if (ctx!=null) { ctx.setWriteDebugMessages(enabled); } } /** * Method to check if GC debug logging is enabled for the active thread's {@link DominoGCContext} * is enabled. * * @return true if enabled */ public static boolean isDebugLoggingEnabled() { DominoGCContext ctx = threadContext.get(); if (ctx==null) { return false; } else { return ctx.isWriteDebugMessages(); } } /** * Method to write a stacktrace to disk right before each native method invocation. Consumes * much performance and is therefore disabled by default and just here to track down * handle panics.
* Stacktraces are written as files domino-jna-stack-<threadid>.txt in the temp directory.
*
* Logging is enabled for the active thread's {@link DominoGCContext}. * * @param log true to log */ public static void setLogCrashingThreadStacktrace(boolean log) { DominoGCContext ctx = threadContext.get(); if (ctx!=null) { ctx.setLogCrashingThreadStackTrace(log); } } /** * Checks whether stacktraces for each native method invocation should be written to disk. Consumes * much performance and is therefore disabled by default and just here to track down * handle panics.
* Stacktraces are written as files domino-jna-stack-<threadid>.txt in the temp directory.
*
* Logging is enabled for the active thread's {@link DominoGCContext}. * * @return true to log */ public static boolean isLogCrashingThreadStacktrace() { DominoGCContext ctx = threadContext.get(); if (ctx==null) { return false; } else { return ctx.isLogCrashingThreadStackTrace(); } } /** * Method to get the current count of open Domino object handles * * @return handle count */ public static int getNumberOfOpenObjectHandles() { DominoGCContext ctx = getThreadContext(); if (PlatformUtils.is64Bit()) { return ctx.getOpenHandlesDominoObjects64().size(); } else { return ctx.getOpenHandlesDominoObjects32().size(); } } /** * Method to get the current count of open Domino memory handles * * @return handle count */ public static int getNumberOfOpenMemoryHandles() { DominoGCContext ctx = getThreadContext(); if (PlatformUtils.is64Bit()) { return ctx.getOpenHandlesMemory64().size(); } else { return ctx.getOpenHandlesMemory32().size(); } } public static class HashKey64 { private Class m_clazz; private long m_handle; public HashKey64(Class clazz, long handle) { m_clazz = clazz; m_handle = handle; } public long getHandle() { return m_handle; } public Class getType() { return m_clazz; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((m_clazz == null) ? 0 : m_clazz.hashCode()); result = prime * result + (int) (m_handle ^ (m_handle >>> 32)); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; HashKey64 other = (HashKey64) obj; if (m_clazz == null) { if (other.m_clazz != null) return false; } else if (!m_clazz.equals(other.m_clazz)) return false; if (m_handle != other.m_handle) return false; return true; } } public static class HashKey32 { private Class m_clazz; private int m_handle; public HashKey32(Class clazz, int handle) { m_clazz = clazz; m_handle = handle; } public int getHandle() { return m_handle; } public Class getType() { return m_clazz; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((m_clazz == null) ? 0 : m_clazz.hashCode()); result = prime * result + m_handle; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; HashKey32 other = (HashKey32) obj; if (m_clazz == null) { if (other.m_clazz != null) return false; } else if (!m_clazz.equals(other.m_clazz)) return false; if (m_handle != other.m_handle) return false; return true; } } /** * Internal method to register a created Notes object that needs to be recycled * * @param clazz class of hash pool * @param obj Notes object */ public static void __objectCreated(Class clazz, IRecyclableNotesObject obj) { DominoGCContext ctx = getThreadContext(); if (obj.isRecycled()) throw new NotesError(0, "Object is already recycled"); if (PlatformUtils.is64Bit()) { HashKey64 key = new HashKey64(clazz, obj.getHandle64()); LinkedHashMap openHandles = ctx.getOpenHandlesDominoObjects64(); IRecyclableNotesObject oldObj = openHandles.put(key, obj); if (oldObj!=null && oldObj!=obj) { throw new IllegalStateException("Duplicate handle detected. Object to store: "+obj+", object found in open handle list: "+oldObj); } } else { HashKey32 key = new HashKey32(clazz, obj.getHandle32()); LinkedHashMap openHandles = ctx.getOpenHandlesDominoObjects32(); IRecyclableNotesObject oldObj = openHandles.put(key, obj); if (oldObj!=null && oldObj!=obj) { throw new IllegalStateException("Duplicate handle detected. Object to store: "+obj+", object found in open handle list: "+oldObj); } } if (ctx.isWriteDebugMessages()) { System.out.println("AutoGC - Added object: "+obj); } } /** * Internal method to register a created Notes object that needs to be recycled * * @param mem Notes object */ public static void __memoryAllocated(IAllocatedMemory mem) { DominoGCContext ctx = getThreadContext(); if (mem.isFreed()) throw new NotesError(0, "Memory is already freed"); if (PlatformUtils.is64Bit()) { LinkedHashMap openHandles = ctx.getOpenHandlesMemory64(); IAllocatedMemory oldObj = openHandles.put(mem.getHandle64(), mem); if (oldObj!=null && oldObj!=mem) { throw new IllegalStateException("Duplicate handle detected. Memory to store: "+mem+", object found in open handle list: "+oldObj); } } else { LinkedHashMap openHandles = ctx.getOpenHandlesMemory32(); IAllocatedMemory oldObj = openHandles.put(mem.getHandle32(), mem); if (oldObj!=null && oldObj!=mem) { throw new IllegalStateException("Duplicate handle detected. Memory to store: "+mem+", object found in open handle list: "+oldObj); } } if (ctx.isWriteDebugMessages()) { System.out.println("AutoGC - Added memory: "+mem); } } /** * Internal method to check whether a 64 bit handle exists * * @param objClazz class of Notes object * @param handle handle * @return Notes object * @throws NotesError if handle does not exist */ public static IRecyclableNotesObject __b64_checkValidObjectHandle(Class objClazz, long handle) { DominoGCContext ctx = getThreadContext(); HashKey64 key = new HashKey64(objClazz, handle); LinkedHashMap openHandles = ctx.getOpenHandlesDominoObjects64(); IRecyclableNotesObject obj = openHandles.get(key); if (obj==null) { throw new NotesError(0, "The provided C handle "+handle+" of object with class "+objClazz.getName()+" does not seem to exist (anymore)."); } else { return obj; } } /** * Internal method to check whether a 64 bit handle exists * * @param memClazz class of Notes object * @param handle handle * @throws NotesError if handle does not exist */ public static void __b64_checkValidMemHandle(Class memClazz, long handle) { DominoGCContext ctx = getThreadContext(); LinkedHashMap openHandles = ctx.getOpenHandlesMemory64(); IAllocatedMemory obj = openHandles.get(handle); if (obj==null) { throw new NotesError(0, "The provided C handle "+handle+" of memory with class "+memClazz.getName()+" does not seem to exist (anymore)."); } } /** * Internal method to check whether a 32 bit handle exists * * @param objClazz class of Notes object * @param handle handle * @return Notes object * @throws NotesError if handle does not exist */ public static IRecyclableNotesObject __b32_checkValidObjectHandle(Class objClazz, int handle) { DominoGCContext ctx = getThreadContext(); LinkedHashMap openHandles = ctx.getOpenHandlesDominoObjects32(); HashKey32 key = new HashKey32(objClazz, handle); IRecyclableNotesObject obj = openHandles.get(key); if (obj==null) { throw new NotesError(0, "The provided C handle "+handle+" of object with class "+objClazz.getName()+" does not seem to exist (anymore)."); } else return obj; } /** * Internal method to check whether a 32 bit handle exists * * @param objClazz class of Notes object * @param handle handle * @throws NotesError if handle does not exist */ public static void __b32_checkValidMemHandle(Class objClazz, int handle) { DominoGCContext ctx = getThreadContext(); LinkedHashMap openHandles = ctx.getOpenHandlesMemory32(); IAllocatedMemory obj = openHandles.get(handle); if (obj==null) { throw new NotesError(0, "The provided C handle "+handle+" of memory with class "+objClazz.getName()+" does not seem to exist (anymore)."); } } /** * Internal method to unregister a created Notes object that was recycled * * @param clazz class of hash pool * @param obj Notes object */ public static void __objectBeeingBeRecycled(Class clazz, IRecyclableNotesObject obj) { DominoGCContext ctx = getThreadContext(); if (obj.isRecycled()) throw new NotesError(0, "Object is already recycled"); if (ctx.isWriteDebugMessages()) { System.out.println("AutoGC - Removing object: "+obj.getClass()+" with handle="+(PlatformUtils.is64Bit() ? obj.getHandle64() : obj.getHandle32())); } if (PlatformUtils.is64Bit()) { HashKey64 key = new HashKey64(clazz, obj.getHandle64()); LinkedHashMap openHandles = ctx.getOpenHandlesDominoObjects64(); openHandles.remove(key); } else { HashKey32 key = new HashKey32(clazz, obj.getHandle32()); LinkedHashMap openHandles = ctx.getOpenHandlesDominoObjects32(); openHandles.remove(key); } } /** * Internal method to unregister a created Notes object that was recycled * * @param mem Notes object */ public static void __memoryBeeingFreed(IAllocatedMemory mem) { DominoGCContext ctx = getThreadContext(); if (mem.isFreed()) throw new NotesError(0, "Memory has already been freed"); if (ctx.isWriteDebugMessages()) { System.out.println("AutoGC - Removing memory: "+mem.getClass()+" with handle="+(PlatformUtils.is64Bit() ? mem.getHandle64() : mem.getHandle32())); } if (PlatformUtils.is64Bit()) { LinkedHashMap openHandles = ctx.getOpenHandlesMemory64(); openHandles.remove(mem.getHandle64()); } else { LinkedHashMap openHandles = ctx.getOpenHandlesMemory32(); openHandles.remove(mem.getHandle32()); } } /** * Use this method to store your own custom values for the duration of the * current {@link NotesGC#runWithAutoGC(Callable)} execution block. * * @param key key * @param value value, implement interface {@link IDisposableCustomValue} to get called for disposal * @return previous value */ public static Object setCustomValue(String key, Object value) { DominoGCContext ctx = getThreadContext(); Map map = ctx.getCustomValues(); return map.put(key, value); } /** * Reads a custom value stored via {@link #setCustomValue(String, Object)} * for the duration of the current {@link #runWithAutoGC(Callable)} * execution block. * * @param key * @return value or null if not set */ public static Object getCustomValue(String key) { DominoGCContext ctx = getThreadContext(); Map map = ctx.getCustomValues(); return map.get(key); } /** * Tests if a custom value has been set via {@link #setCustomValue(String, Object)}. * * @param key key * @return true if value is set */ public boolean hasCustomValue(String key) { DominoGCContext ctx = getThreadContext(); Map map = ctx.getCustomValues(); return map.containsKey(key); } /** * Throws an exception when the code is currently not running in a runWithAutoGC block */ public static void ensureRunningInAutoGC() { DominoGCContext ctx = threadContext.get(); if (ctx==null) { throw new IllegalStateException("Thread is not enabled for auto GC. Either run your code via runWithAutoGC(Callable) or via try-with-resources on the object returned by initThread()."); } } /** * Method to check whether the current thread is already running in * an auto GC block * * @return true if in auto GC */ public static boolean isAutoGCActive() { return threadContext.get() != null; } /** * When using {@link NotesGC#setCustomValue(String, Object)} to store your own * values, use this * interface for your value to get called for disposal when the {@link NotesGC#runWithAutoGC(Callable)} * block is finished. Otherwise the value is just removed from the intermap map. * * @author Karsten Lehmann */ public static interface IDisposableCustomValue { public void dispose(); } /** * Runs a piece of code and automatically disposes any allocated Notes objects at the end. * The method supported nested calls. * * @param callable code to execute * @return computation result * @throws Exception in case of errors * * @param return value type of code to be run */ public static T runWithAutoGC(final Callable callable) throws Exception { try (DominoGCContext ctx = initThread()) { return AccessController.doPrivileged(new PrivilegedAction() { @Override public T run() { try { return callable.call(); } catch (Exception e) { if (e instanceof RuntimeException) { throw (RuntimeException) e; } else { throw new NotesError(0, "Error during code execution", e); } } } }); } } /** * Initializes the current thread for Domino JNA C and memory resource * tracking.
*
* The returned {@link DominoGCContext} must be closed to free up all allocated resources. * Otherwise the client/server will run out of handles sooner or later!
*
* It is recommented to use a try-with-resources block to ensure calling the close() method * even in case of execution errors, e.g.
*
* * try (DominoGCContext ctx = initThread()) {
*    // use Domino JNA classes, e.g.
*    NotesDatabase db = new NotesDatabase("", "names.nsf", "");
*
* } catch (Exception e) {
*    log(Level.SEVERE, "Error accessing Domino data", e);
* }
*
*
* Nested invocation of this method is supported. The current implementation only * frees up resources when calling {@link DominoGCContext#close()} on the outer most * context. Other calls are ignored. * * @return garbage collection context */ public static DominoGCContext initThread() { DominoGCContext ctx = threadContext.get(); if (ctx==null) { NotesNativeAPI.initialize(); ctx = new DominoGCContext(null); threadContext.set(ctx); return ctx; } else { return new DominoGCContext(ctx); } } /** * Domino handle collection context to collect all allocated C object * and memory handles for the current thread. * * @author Karsten Lehmann */ public static class DominoGCContext implements AutoCloseable { private DominoGCContext m_parentCtx; private Thread m_parentThread; private Map m_activeAutoGCCustomValues; //maps with open handles; using LinkedHashMap to keep insertion order for the keys and disposed in reverse order private LinkedHashMap m_b32OpenHandlesDominoObjects; private LinkedHashMap m_b32OpenHandlesMemory; private LinkedHashMap m_b64OpenHandlesDominoObjects; private LinkedHashMap m_b64OpenHandlesMemory; private boolean m_writeDebugMessages; private boolean m_logCrashingThreadStackTrace; private DominoGCContext(DominoGCContext parentCtx) { m_parentCtx = parentCtx; m_parentThread = Thread.currentThread(); } /** * Returns the parent GC context if nested calls on {@link NotesGC#initThread()} * are used. * * @return parent context or null for top context */ public DominoGCContext getParentContext() { return m_parentCtx; } /** * Returns true if this GC context is the first created for the current * thread and false if it's a nested context. * * @return true if top context */ public boolean isTopContext() { return m_parentCtx==null; } private void checkValidThread() { if (!m_parentThread.equals(Thread.currentThread())) { throw new IllegalStateException("This context cannot be used across threads"); } } public boolean isWriteDebugMessages() { if (m_parentCtx!=null) { return m_parentCtx.isWriteDebugMessages(); } return m_writeDebugMessages; } public void setWriteDebugMessages(boolean b) { if (m_parentCtx!=null) { m_parentCtx.setWriteDebugMessages(b); return; } m_writeDebugMessages = b; } public boolean isLogCrashingThreadStackTrace() { if (m_parentCtx!=null) { return m_parentCtx.isLogCrashingThreadStackTrace(); } return m_logCrashingThreadStackTrace; } public void setLogCrashingThreadStackTrace(boolean b) { if (m_parentCtx!=null) { m_parentCtx.setLogCrashingThreadStackTrace(b); return; } m_logCrashingThreadStackTrace = b; } private Map getCustomValues() { checkValidThread(); if (m_parentCtx!=null) { return m_parentCtx.getCustomValues(); } if (m_activeAutoGCCustomValues==null) { m_activeAutoGCCustomValues = new HashMap<>(); } return m_activeAutoGCCustomValues; } private LinkedHashMap getOpenHandlesDominoObjects32() { checkValidThread(); if (m_parentCtx!=null) { return m_parentCtx.getOpenHandlesDominoObjects32(); } if (m_b32OpenHandlesDominoObjects==null) { m_b32OpenHandlesDominoObjects = new LinkedHashMap<>(); } return m_b32OpenHandlesDominoObjects; } private LinkedHashMap getOpenHandlesMemory32() { checkValidThread(); if (m_parentCtx!=null) { return m_parentCtx.getOpenHandlesMemory32(); } if (m_b32OpenHandlesMemory==null) { m_b32OpenHandlesMemory = new LinkedHashMap<>(); } return m_b32OpenHandlesMemory; } private LinkedHashMap getOpenHandlesDominoObjects64() { checkValidThread(); if (m_parentCtx!=null) { return m_parentCtx.getOpenHandlesDominoObjects64(); } if (m_b64OpenHandlesDominoObjects==null) { m_b64OpenHandlesDominoObjects = new LinkedHashMap<>(); } return m_b64OpenHandlesDominoObjects; } private LinkedHashMap getOpenHandlesMemory64() { checkValidThread(); if (m_parentCtx!=null) { return m_parentCtx.getOpenHandlesMemory64(); } if (m_b64OpenHandlesMemory==null) { m_b64OpenHandlesMemory = new LinkedHashMap<>(); } return m_b64OpenHandlesMemory; } @Override public void close() throws Exception { checkValidThread(); if (!isTopContext()) { //don't free up resources in nested calls on NotesGC.initThread() return; } if (PlatformUtils.is64Bit()) { { //recycle created Domino objects if (m_b64OpenHandlesDominoObjects!=null && !m_b64OpenHandlesDominoObjects.isEmpty()) { Entry[] mapEntries = m_b64OpenHandlesDominoObjects.entrySet().toArray(new Entry[m_b64OpenHandlesDominoObjects.size()]); if (mapEntries.length>0) { if (m_writeDebugMessages) { System.out.println("AutoGC - Auto-recycling "+mapEntries.length+" Domino objects:"); } for (int i=mapEntries.length-1; i>=0; i--) { Entry currEntry = mapEntries[i]; IRecyclableNotesObject obj = currEntry.getValue(); try { if (!obj.isRecycled()) { if (m_writeDebugMessages) { System.out.println("AutoGC - Auto-recycling "+obj); } obj.recycle(); } } catch (Throwable e) { e.printStackTrace(); } m_b64OpenHandlesDominoObjects.remove(currEntry.getKey()); } if (m_writeDebugMessages) { System.out.println("AutoGC - Done auto-recycling "+mapEntries.length+" Domino objects"); } m_b64OpenHandlesDominoObjects.clear(); m_b64OpenHandlesDominoObjects = null; } } } { //dispose allocated memory if (m_b64OpenHandlesMemory!=null && !m_b64OpenHandlesMemory.isEmpty()) { Entry[] mapEntries = m_b64OpenHandlesMemory.entrySet().toArray(new Entry[m_b64OpenHandlesMemory.size()]); if (mapEntries.length>0) { if (m_writeDebugMessages) { System.out.println("AutoGC - Freeing "+mapEntries.length+" memory handles"); } for (int i=mapEntries.length-1; i>=0; i--) { Entry currEntry = mapEntries[i]; IAllocatedMemory obj = currEntry.getValue(); try { if (!obj.isFreed()) { if (m_writeDebugMessages) { System.out.println("AutoGC - Freeing "+obj); } obj.free(); } } catch (Throwable e) { e.printStackTrace(); } m_b64OpenHandlesMemory.remove(currEntry.getKey()); } if (m_writeDebugMessages) { System.out.println("AutoGC - Done freeing "+mapEntries.length+" memory handles"); } m_b64OpenHandlesMemory.clear(); m_b64OpenHandlesMemory = null; } } } } else { { if (m_b32OpenHandlesDominoObjects!=null && !m_b32OpenHandlesDominoObjects.isEmpty()) { //recycle created Domino objects Entry[] mapEntries = m_b32OpenHandlesDominoObjects.entrySet().toArray(new Entry[m_b32OpenHandlesDominoObjects.size()]); if (mapEntries.length>0) { if (m_writeDebugMessages) { System.out.println("AutoGC - Recycling "+mapEntries.length+" Domino objects:"); } for (int i=mapEntries.length-1; i>=0; i--) { Entry currEntry = mapEntries[i]; IRecyclableNotesObject obj = currEntry.getValue(); try { if (!obj.isRecycled()) { if (m_writeDebugMessages) { System.out.println("AutoGC - Recycling "+obj); } obj.recycle(); } } catch (Throwable e) { e.printStackTrace(); } m_b32OpenHandlesDominoObjects.remove(currEntry.getKey()); } if (m_writeDebugMessages) { System.out.println("AutoGC - Done recycling "+mapEntries.length+" memory handles"); } m_b32OpenHandlesDominoObjects.clear(); m_b32OpenHandlesDominoObjects = null; } } } { if (m_b32OpenHandlesMemory!=null && !m_b32OpenHandlesMemory.isEmpty()) { //dispose allocated memory Entry[] mapEntries = m_b32OpenHandlesMemory.entrySet().toArray(new Entry[m_b32OpenHandlesMemory.size()]); if (mapEntries.length>0) { if (m_writeDebugMessages) { System.out.println("AutoGC - Freeing "+mapEntries.length+" memory handles"); } for (int i=mapEntries.length-1; i>=0; i--) { Entry currEntry = mapEntries[i]; IAllocatedMemory obj = currEntry.getValue(); try { if (!obj.isFreed()) { if (m_writeDebugMessages) { System.out.println("AutoGC - Freeing "+obj); } obj.free(); } } catch (Throwable e) { e.printStackTrace(); } m_b32OpenHandlesMemory.remove(currEntry.getKey()); } if (m_writeDebugMessages) { System.out.println("AutoGC - Done freeing "+mapEntries.length+" memory handles"); } m_b32OpenHandlesMemory.clear(); m_b32OpenHandlesMemory = null; } } } } if (m_activeAutoGCCustomValues!=null) { cleanupCustomValues(m_activeAutoGCCustomValues); m_activeAutoGCCustomValues.clear(); m_activeAutoGCCustomValues = null; } threadContext.set(null); } } private static void cleanupCustomValues(Map customValues) { for (Entry currEntry : customValues.entrySet()) { Object currVal = currEntry.getValue(); if (currVal instanceof IDisposableCustomValue) { try { ((IDisposableCustomValue)currVal).dispose(); } catch (Exception e) { //give access to this exception via special (optional) PrintWriter, //but continue with the loop Object out = customValues.get("NotesGC.CustomValueDisposeOut"); if (out instanceof PrintWriter) { e.printStackTrace((PrintWriter) out); } } } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy