Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions wrapper/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,56 @@ java {
}
}

// Create a separate source set for Java 11+ specific code (e.g., java.lang.ref.Cleaner)
val java11 = sourceSets.create("java11") {
java {
srcDir("src/main/java11")
}
// Make java11 source set depend on main source set
compileClasspath += sourceSets.main.get().output
}

dependencies {
add(java11.implementationConfigurationName, "org.checkerframework:checker-qual:3.52.0")
}

// Configure the java11 source set to compile with Java 11
tasks.named<JavaCompile>(java11.compileJavaTaskName) {
javaCompiler.set(javaToolchains.compilerFor {
languageVersion.set(JavaLanguageVersion.of(11))
})
options.release.set(11)
// Ensure main classes are compiled before java11 classes
dependsOn(tasks.compileJava)
}

fun CopySpec.addMultiReleaseContents() {
into("META-INF/versions/11") {
from(java11.output)
}
}

// Add java11 compiled classes to the main JAR
tasks.jar {
dependsOn(tasks.named(java11.compileJavaTaskName))

// Add multi-release content after bnd processing
doLast {
val java11Dir = java11.output.classesDirs.files.first()
if (java11Dir.exists()) {
ant.withGroovyBuilder {
"jar"("destfile" to archiveFile.get().asFile, "update" to true) {
"zipfileset"("dir" to java11Dir, "prefix" to "META-INF/versions/11")
}
}
}
}

manifest {
attributes["Multi-Release"] = "true"
}
}

tasks.named("sourcesJar") {
dependsOn("preprocessVersion")
}
Expand Down Expand Up @@ -248,6 +298,8 @@ tasks.jar {
"""
-exportcontents: software.*
-removeheaders: Created-By
-noclassforname: true
-noextraheaders: true
Bundle-Description: Amazon Web Services (AWS) Advanced JDBC Wrapper Driver
Bundle-DocURL: https://github.com/aws/aws-advanced-jdbc-wrapper
Bundle-Vendor: Amazon Web Services (AWS)
Expand Down
30 changes: 30 additions & 0 deletions wrapper/src/main/java/software/amazon/jdbc/util/LazyCleaner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package software.amazon.jdbc.util;

public interface LazyCleaner {

interface Cleanable {
void clean() throws Exception;
}

interface CleaningAction {
void onClean(boolean leak) throws Exception;
}

Cleanable register(Object obj, CleaningAction action);
}
210 changes: 210 additions & 0 deletions wrapper/src/main/java/software/amazon/jdbc/util/LazyCleanerImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/* changes were made to move it into the software.amazon.jdbc.util package
*
* Copyright 2022 Juan Lopes
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package software.amazon.jdbc.util;

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.time.Duration;
import java.util.concurrent.ForkJoinPool;
import java.util.function.BooleanSupplier;
import java.util.logging.Level;
import java.util.logging.Logger;

public class LazyCleanerImpl implements LazyCleaner {
private static final Logger LOGGER = Logger.getLogger(LazyCleanerImpl.class.getName());

private static final LazyCleanerImpl instance = new LazyCleanerImpl(
"AWS-JDBC-Cleaner",
Duration.ofMillis(Long.getLong("aws.jdbc.config.cleanup.thread.ttl", 30000))
);

private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
private final String threadName;
private final Duration threadTtl;
private boolean threadRunning;
private Node<?> first;

public LazyCleanerImpl(String threadName, Duration threadTtl) {
this.threadName = threadName;
this.threadTtl = threadTtl;
}

public static LazyCleanerImpl getInstance() {
return instance;
}

@Override
public Cleanable register(Object obj, CleaningAction action) {
return add(new Node<>(obj, action));
}

public synchronized boolean isThreadRunning() {
return threadRunning;
}

private synchronized boolean checkEmpty() {
if (first == null) {
threadRunning = false;
return true;
}
return false;
}

private synchronized <T> Node<T> add(Node<T> node) {
if (first != null) {
node.next = first;
first.prev = node;
}
first = node;
if (!threadRunning) {
threadRunning = startThread();
}
return node;
}

private static class RefQueueBlocker<T> implements ForkJoinPool.ManagedBlocker {
private final ReferenceQueue<T> queue;
private final String threadName;
private Reference<? extends T> ref;
private final long blockTimeoutMillis;
private final BooleanSupplier shouldTerminate;

RefQueueBlocker(ReferenceQueue<T> queue, String threadName, Duration blockTimeout, BooleanSupplier shouldTerminate) {
this.queue = queue;
this.threadName = threadName;
this.blockTimeoutMillis = blockTimeout.toMillis();
this.shouldTerminate = shouldTerminate;
}

@Override
public boolean isReleasable() {
if (ref != null || shouldTerminate.getAsBoolean()) {
return true;
}
ref = queue.poll();
return ref != null;
}

@Override
public boolean block() throws InterruptedException {
if (isReleasable()) {
return true;
}
Thread currentThread = Thread.currentThread();
String oldName = currentThread.getName();
try {
currentThread.setName(threadName);
ref = queue.remove(blockTimeoutMillis);
} finally {
currentThread.setName(oldName);
}
return false;
}

public Reference<? extends T> drainOne() {
Reference<? extends T> ref = this.ref;
this.ref = null;
return ref;
}
}

private boolean startThread() {
ForkJoinPool.commonPool().execute(() -> {
Thread.currentThread().setContextClassLoader(null);
RefQueueBlocker<Object> blocker = new RefQueueBlocker<>(queue, threadName, threadTtl, this::checkEmpty);
while (!checkEmpty()) {
try {
ForkJoinPool.managedBlock(blocker);
@SuppressWarnings("unchecked")
Node<Object> ref = (Node<Object>) blocker.drainOne();
if (ref != null) {
ref.onClean(true);
}
} catch (InterruptedException e) {
if (!blocker.isReleasable()) {
LOGGER.log(Level.FINE, "Got interrupt and the cleanup queue is empty, will terminate the cleanup thread");
break;
}
LOGGER.log(Level.FINE, "Got interrupt and the cleanup queue is NOT empty. Will ignore the interrupt");
} catch (Throwable e) {
LOGGER.log(Level.WARNING, "Unexpected exception while executing onClean", e);
}
}
});
return true;
}

private synchronized boolean remove(Node<?> node) {
if (node.next == node) {
return false;
}
if (first == node) {
first = node.next;
}
if (node.next != null) {
node.next.prev = node.prev;
}
if (node.prev != null) {
node.prev.next = node.next;
}
node.next = node;
node.prev = node;
return true;
}

private class Node<T> extends PhantomReference<T> implements Cleanable, CleaningAction {
private final CleaningAction action;
private Node<?> prev;
private Node<?> next;

Node(T referent, CleaningAction action) {
super(referent, queue);
this.action = action;
}

@Override
public void clean() throws Exception {
onClean(false);
}

@Override
public void onClean(boolean leak) throws Exception {
if (!remove(this)) {
return;
}
action.onClean(leak);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package software.amazon.jdbc.util;

import java.lang.ref.Cleaner;
import java.time.Duration;
import java.util.concurrent.ThreadFactory;

/**
* Java 11+ implementation using java.lang.ref.Cleaner.
*/
public class LazyCleanerImpl implements LazyCleaner {
private static final LazyCleanerImpl instance =
new LazyCleanerImpl(
Duration.ofMillis(Long.getLong("aws.jdbc.cleanup.thread.ttl", 30000)),
"AWS-JDBC-Cleaner"
);

private final Cleaner cleaner;

public static LazyCleanerImpl getInstance() {
return instance;
}

public LazyCleanerImpl(Duration threadTtl, final String threadName) {
this(threadTtl, runnable -> {
Thread thread = new Thread(runnable, threadName);
thread.setDaemon(true);
return thread;
});
}

private LazyCleanerImpl(Duration threadTtl, ThreadFactory threadFactory) {
this.cleaner = Cleaner.create(threadFactory);
}

public Cleanable register(Object obj, CleaningAction action) {
return new CleanableWrapper(cleaner.register(obj, () -> {
try {
action.onClean(true);
} catch (Throwable e) {
// Cleaner swallows exceptions, but we should at least log them
// The logging is handled by the action itself
}
}), action);
}

private static class CleanableWrapper implements Cleanable {
private final java.lang.ref.Cleaner.Cleanable cleanable;
private final CleaningAction action;
private volatile boolean cleaned = false;

CleanableWrapper(java.lang.ref.Cleaner.Cleanable cleanable, CleaningAction action) {
this.cleanable = cleanable;
this.action = action;
}

@Override
public void clean() throws Exception {
if (!cleaned) {
cleaned = true;
cleanable.clean();
action.onClean(false);
}
}
}
}
Loading
Loading