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

com.microsoft.azure.javamsalruntime.HandleBase Maven / Gradle / Ivy

Go to download

The Java/MSALRuntime interop layer facilitates communication between MSAL Java and the MSALRuntime API, allowing developers to easily access WAM from a Java program

The newest version!
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

package com.microsoft.azure.javamsalruntime;

import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.LongByReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

/**
 * Interface used to define common behavior for the various classes that represent MSALRuntime
 * handles
 * 

* Extends LongByReference to allow JNA to convert it to an equivalent of MSALRuntime's *HANDLE * types, and implements AutoCloseable in order to be used in a try-with-resources block */ abstract class HandleBase extends LongByReference implements AutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(HandleBase.class); protected LongByReference msalRuntimeHandle; ReleaseMethod releaseMethod; /** * Thread that manages cleaning up Handles and their PhantomReferences */ private final HandleFinalizerThread HANDLE_FINALIZER_THREAD = new HandleFinalizerThread(); HandleBase(ReleaseMethod releaseMethod) { this.msalRuntimeHandle = new LongByReference(); this.releaseMethod = releaseMethod; HANDLE_FINALIZER_THREAD.addReference(this, msalRuntimeHandle, releaseMethod); } HandleBase(LongByReference msalRuntimeHandle, ReleaseMethod releaseMethod) { this.msalRuntimeHandle = msalRuntimeHandle; this.releaseMethod = releaseMethod; HANDLE_FINALIZER_THREAD.addReference(this, msalRuntimeHandle, releaseMethod); } /** * Returns the handle value that this Handle instance represents. This value is generally set by * MSALRuntime, and is how the interop can access data managed by MSALRuntime */ public long value() { return this.getValue(); } /** * Handle objects extend LongByReference, so they will have a default value of 0 *

* Various MSALRuntime APIs take a fresh (value = 0) Handle as a parameter and sets it to some * non-zero value, allowing it to act as a reference to some underlying data managed by * MSALRuntime * * @return boolean true if this handle was set to some non-zero value, false otherwise */ boolean isHandleValid() { return value() != 0; } /** * All Java objects created by the interop will either be cleaned up by the JVM's garbage * collector or by the operating system if the JVM shuts down, however the MSALRuntime handles * and their underlying data won't be since the JVM doesn't actually know about that data

* So, to avoid memory leaks all MSALRuntime handles must eventually be 'released' by calling * the appropriate MSALRuntime MSALRuntime_RELEASE* API for that type of handle

In most * cases a handle can be released immediately after using it to retrieve some data, however * there are some scenarios where handles must be stored for an indefinite amount of time and we * must rely on Java's PhantomReference and our AsyncHandleFinalizerThread */ public void release() { if (isHandleValid()) { try { // Release handle using the MSALRuntime API set when this object was created MsalRuntimeInterop.ERROR_HELPER.checkMsalRuntimeError(releaseMethod.release(this.value())); // Set local handle to null, to indicate that it has been released and prevent // attempts to use it again this.msalRuntimeHandle = null; } catch (MsalInteropException e) { throw e; } catch (Exception e) { MsalRuntimeInterop.ERROR_HELPER.logUnknownErrorReleasingHandle(e); } } } /** * Used to automatically release handles created in a try-with-resources block */ @Override public void close() { release(); } /** * Helper method for returning a String from MSALRuntime *

* Any MSALRuntime API that populates a String requires two calls: the first is needed to figure * out the size of the String, and the second call actually populates the String * * @param handle a handle representing some block of data which (should) contain a * String * @param getMSALRuntimeString the MSALRuntime API that we will call in order to retrieve a * String * @return the String retrieved from MSALRuntime */ static String getString(HandleBase handle, GetMsalRuntimeString getMSALRuntimeString) { IntByReference bufferSize = new IntByReference(0); // First, we must call the MSALRuntime API to populate the bufferSize with the size of the // String boolean insufficientBufferError = MsalRuntimeInterop.ERROR_HELPER.checkResponseStatus( getMSALRuntimeString.getString(handle, null, bufferSize), MsalRuntimeResponseStatus.MSALRUNTIME_RESPONSE_STATUS_INSUFFICIENTBUFFER); // If we get an insufficient buffer error like we expect, then the bufferSize should have // been populated and we can get the actual String if (insufficientBufferError) { if (bufferSize.getValue() != 0) { // Create a memory location the same size as the String we want to retrieve Pointer stringMemoryLocation = new Memory(Native.WCHAR_SIZE * (bufferSize.getValue())); // Send that memory location to MSALRuntime to copy the String into MsalRuntimeInterop.ERROR_HELPER.checkMsalRuntimeError( getMSALRuntimeString.getString(handle, stringMemoryLocation, bufferSize)); // Retrieve the copied string from the memory location return stringMemoryLocation.getWideString(0); } else { // String of size 0, nothing to retrieve LOG.warn("Buffer size is 0"); return ""; } } else { LOG.warn("Could not parse string."); } return ""; } /** * Interface which allows an MSALRuntime_RELEASE* function to be passed as a parameter, allowing * all MSALRuntime release methods to be called by this class */ @FunctionalInterface interface ReleaseMethod { ErrorHandle release(long handle); } /** * Interface which allows data to be retrieved from any MSALRuntime API which populates a String */ @FunctionalInterface interface GetMsalRuntimeString { ErrorHandle getString(HandleBase handle, Pointer stringReference, IntByReference bufferSize); } /** * Class used to represent a phantom reference to a Handle instance, allowing handles to be * released via AsyncHandleFinalizerThread when the phantom reference is the instance's only * reference *

* This class cannot have reference to the actual Handle instance, since that would create a * strong reference that is never removed, so it must hold copies of any Handle data needed for * calling the MSALRuntime release API */ class HandleFinalizer extends PhantomReference { private final Logger LOG = LoggerFactory.getLogger(HandleFinalizer.class); private LongByReference finalizerMsalRuntimeHandle; private ReleaseMethod finalizerReleaseMethod; private HandleFinalizer( HandleBase handle, LongByReference msalRuntimeHandle, ReleaseMethod releaseMethod, ReferenceQueue queue) { super(handle, queue); // Copy Handle's value and release methods, so the this.finalizerMsalRuntimeHandle = msalRuntimeHandle; this.finalizerReleaseMethod = releaseMethod; } private void release() { LOG.debug("Releasing a handle via PhantomReference"); if (finalizerMsalRuntimeHandle != null && finalizerMsalRuntimeHandle.getValue() != 0) { try { // Release handle using the MSALRuntime API set when this object was created MsalRuntimeInterop.ERROR_HELPER.checkMsalRuntimeError( finalizerReleaseMethod.release(finalizerMsalRuntimeHandle.getValue())); // Set local handle to null, to indicate that it has been released and prevent // attempts to use it again this.finalizerMsalRuntimeHandle = null; } catch (MsalInteropException e) { throw e; } catch (Exception e) { MsalRuntimeInterop.ERROR_HELPER.logUnknownErrorReleasingHandle(e); } } } } /** * Thread which will start when the first Handle is created. *

* This thread will be responsible for releasing handles in scenarios where we can't release * them immediately, and as a fail-safe in case a handle isn't released properly */ class HandleFinalizerThread extends Thread { private final Logger LOG = LoggerFactory.getLogger(HandleFinalizerThread.class); private ReferenceQueue handleReferenceQueue = new ReferenceQueue<>(); HandleFinalizerThread() { setDaemon(true); } /** * Create a new PhantomReference to a give Handle by creating a HandleFinalizer with this * Handle's value and release method

When the PhantomReference is the only remaining * reference to the Handle, the HandleFinalizer will appear in handleReferenceQueue and the * Handle will be released */ void addReference(HandleBase handle, LongByReference msalRuntimeHandle, ReleaseMethod releaseMethod) { // When the first Handle is created, start the finalizer thread that all Handles share if (!HANDLE_FINALIZER_THREAD.isAlive()) { // Set up unknown exception handling for the thread, to ensure as much as possible // gets released cleanly HANDLE_FINALIZER_THREAD.setUncaughtExceptionHandler((th, ex) -> { LOG.error( "Unexpected exception in HandleFinalizerThread with {} open async handles. Will attempt to " + "cancel any async operations before stopping thread.", MsalRuntimeFuture.msalRuntimeFutures.size()); for (MsalRuntimeFuture future : MsalRuntimeFuture.msalRuntimeFutures.values()) { future.cancelAsyncOperation(); future.handle.release(); } }); HANDLE_FINALIZER_THREAD.start(); } new HandleFinalizer(handle, msalRuntimeHandle, releaseMethod, handleReferenceQueue); } @Override public void run() { try { while (true) { // Although this is an infinite loop, ReferenceQueue's remove() method causes it // to wait until an entry appears in handleReferenceQueue. This will only happen // when a Handle is reachable only through a PhantomReference, and can therefore // be released HandleFinalizer handleFinalizer = (HandleFinalizer)handleReferenceQueue.remove(); LOG.info("Found Handle with no references, closing."); handleFinalizer.release(); } } catch (InterruptedException e) { // Ideally, this will only run when the entire program shuts down, and most handles // will be released via their close() method if their in a try-with-resources block // // MsalRuntimeFuture.msalRuntimeFutures allows us to track async handles, so we can // at least guarantee they always get canceled/released LOG.error( "HandleFinalizerThread interrupted with {} open async handles. Will attempt to cancel any async " + "operations before stopping thread.", MsalRuntimeFuture.msalRuntimeFutures.size()); for (MsalRuntimeFuture future : MsalRuntimeFuture.msalRuntimeFutures.values()) { future.cancelAsyncOperation(); future.handle.release(); } } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy