Skip to content

Commit e26f0d0

Browse files
sabrinajleeSabrina Lee
andauthored
Implement cleaner to replace deprecated finalizers (#893)
The cleaner is the replacement for the deprecated finalize() method for cleaning objects before garbage collection. Each class that was previously using finalize() will have an anonymous function that cleans up the necessary resources. The number of cleaner threads can be set by a property with the default value being 2. The object and its anonymous function are registered to one of the cleaner threads in round robin order. Signed-off-by: Sabrina Lee <[email protected]> Co-authored-by: Sabrina Lee <[email protected]>
1 parent a2cc257 commit e26f0d0

File tree

10 files changed

+125
-73
lines changed

10 files changed

+125
-73
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- [Run All Tests](#run-all-tests)
77
- [Run Single Test](#run-single-test)
88
- [OpenJCEPlus and OpenJCEPlusFIPS Provider SDK Installation](#openjceplus-and-openjceplusfips-provider-sdk-installation)
9+
- [Configuration Options](#configuration-options)
910
- [Features and Algorithms](#features-and-algorithms)
1011
- [Contributions](#contributions)
1112

@@ -263,6 +264,13 @@ take effect.
263264
```console
264265
-Djgskit.library.path=$ANYDIRECTORY
265266
```
267+
## Configuration Options
268+
269+
The following properties can be used to configure application behavior at runtime.
270+
271+
| Property | Use Case |
272+
|----------|----------|
273+
| `-Dopenjceplus.cleaners.num=<number_cleaner_threads>` | The cleaner is used for cleaning up native memory no longer in use by OpenJCEPlus and OpenJCEPlusFIPS providers. This option sets the number of cleaner threads to improve cleaning efficiency, particularly useful when encountering `Out Of Memory` (OOM) errors. Default value is `2`. |
266274

267275
# Features And Algorithms
268276

@@ -423,7 +431,6 @@ AES Key Wrap based on NIST SP800-38F.
423431

424432
Code does not allow the specification of an IV. However, it will return the default ICV as defined in the NIST SP800-38F.
425433

426-
427434
# Contributions
428435

429436
The following contribution guidelines should be followed:

src/main/java/com/ibm/crypto/plus/provider/DSASignature.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright IBM Corp. 2023
2+
* Copyright IBM Corp. 2023, 2025
33
*
44
* This code is free software; you can redistribute it and/or modify it
55
* under the terms provided by IBM in the LICENSE file that accompanied
@@ -28,7 +28,7 @@ abstract class DSASignature extends SignatureSpi {
2828
DSASignature(OpenJCEPlusProvider provider, String ockDigestAlgo) {
2929
try {
3030
this.provider = provider;
31-
this.signature = Signature.getInstance(provider.getOCKContext(), ockDigestAlgo);
31+
this.signature = Signature.getInstance(provider.getOCKContext(), ockDigestAlgo, provider);
3232
} catch (Exception e) {
3333
throw provider.providerException("Failed to initialize DSA signature", e);
3434
}

src/main/java/com/ibm/crypto/plus/provider/ECDSASignature.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright IBM Corp. 2023, 2024
2+
* Copyright IBM Corp. 2023, 2025
33
*
44
* This code is free software; you can redistribute it and/or modify it
55
* under the terms provided by IBM in the LICENSE file that accompanied
@@ -32,7 +32,7 @@ abstract class ECDSASignature extends SignatureSpi {
3232
ECDSASignature(OpenJCEPlusProvider provider, String ockDigestAlgo) {
3333
try {
3434
this.provider = provider;
35-
this.signature = Signature.getInstance(provider.getOCKContext(), ockDigestAlgo);
35+
this.signature = Signature.getInstance(provider.getOCKContext(), ockDigestAlgo, provider);
3636
} catch (Exception e) {
3737
throw provider.providerException("Failed to initialize ECDSA signature", e);
3838
}

src/main/java/com/ibm/crypto/plus/provider/MessageDigest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ abstract class MessageDigest extends MessageDigestSpi implements Cloneable {
1919
MessageDigest(OpenJCEPlusProvider provider, String ockDigestAlgo) {
2020
try {
2121
this.provider = provider;
22-
this.digest = Digest.getInstance(provider.getOCKContext(), ockDigestAlgo);
22+
this.digest = Digest.getInstance(provider.getOCKContext(), ockDigestAlgo, provider);
2323
} catch (Exception e) {
2424
throw provider.providerException("Failure in MessageDigest", e);
2525
}

src/main/java/com/ibm/crypto/plus/provider/OpenJCEPlus.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import java.util.List;
2525
import java.util.Map;
2626
import javax.crypto.SecretKey;
27-
import sun.security.util.Debug;
2827

2928
@SuppressWarnings({"removal", "deprecation"})
3029
public final class OpenJCEPlus extends OpenJCEPlusProvider {
@@ -65,9 +64,6 @@ public final class OpenJCEPlus extends OpenJCEPlusProvider {
6564
// to find ourselves or run the risk of not being in the list.
6665
private static volatile OpenJCEPlus instance;
6766

68-
// User enabled debugging
69-
private static Debug debug = Debug.getInstance(DEBUG_VALUE);
70-
7167
private static boolean ockInitialized = false;
7268
private static OCKContext ockContext;
7369
private static Map<String, String> attrs;

src/main/java/com/ibm/crypto/plus/provider/OpenJCEPlusFIPS.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import java.util.List;
2525
import java.util.Map;
2626
import javax.crypto.SecretKey;
27-
import sun.security.util.Debug;
2827

2928
@SuppressWarnings({"removal", "deprecation"})
3029
public final class OpenJCEPlusFIPS extends OpenJCEPlusProvider {
@@ -64,9 +63,6 @@ public final class OpenJCEPlusFIPS extends OpenJCEPlusProvider {
6463
// to find ourselves or run the risk of not being in the list.
6564
private static volatile OpenJCEPlusFIPS instance;
6665

67-
// User enabled debugging
68-
private static Debug debug = Debug.getInstance(DEBUG_VALUE);
69-
7066
private static boolean ockInitialized = false;
7167
private static OCKContext ockContext;
7268

src/main/java/com/ibm/crypto/plus/provider/OpenJCEPlusProvider.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
package com.ibm.crypto.plus.provider;
1010

1111
import com.ibm.crypto.plus.provider.ock.OCKContext;
12+
import java.lang.ref.Cleaner;
1213
import java.security.ProviderException;
14+
import java.util.concurrent.atomic.AtomicInteger;
15+
import sun.security.util.Debug;
1316

1417
// Internal interface for OpenJCEPlus and OpenJCEPlus implementation classes.
1518
// Implemented as an abstract class rather than an interface so that
@@ -26,10 +29,40 @@ public abstract class OpenJCEPlusProvider extends java.security.Provider {
2629

2730
static final String DEBUG_VALUE = "jceplus";
2831

32+
private final Cleaner[] cleaners;
33+
34+
private final int DEFAULT_NUM_CLEANERS = 2;
35+
36+
private final int numCleaners;
37+
38+
private AtomicInteger count = new AtomicInteger(0);
39+
40+
protected static final Debug debug = Debug.getInstance(DEBUG_VALUE);
41+
2942
OpenJCEPlusProvider(String name, String info) {
3043
super(name, PROVIDER_VER, info);
44+
45+
numCleaners = Integer.getInteger("openjceplus.cleaners.num", DEFAULT_NUM_CLEANERS);
46+
if (numCleaners < 1){
47+
throw new IllegalArgumentException(numCleaners + " is an invalid number of cleaner threads, must be at least 1.");
48+
}
49+
50+
cleaners = new Cleaner[numCleaners];
51+
for (int i = 0; i < numCleaners; i++) {
52+
final Cleaner cleaner = Cleaner.create();
53+
cleaners[i] = cleaner;
54+
}
3155
}
3256

57+
public void registerCleanable(Object owner, Runnable cleanAction) {
58+
Cleaner cleaner = cleaners[Math.abs(count.getAndIncrement() % numCleaners)];
59+
cleaner.register(owner, cleanAction);
60+
}
61+
62+
public static Debug getDebug() {
63+
return debug;
64+
}
65+
3366
// Get OCK context for crypto operations
3467
//
3568
abstract OCKContext getOCKContext();

src/main/java/com/ibm/crypto/plus/provider/RSASignature.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ abstract class RSASignature extends SignatureSpi {
3535
try {
3636
this.provider = provider;
3737
this.ockDigestAlgo = ockDigestAlgo;
38-
this.signature = Signature.getInstance(provider.getOCKContext(), ockDigestAlgo);
38+
this.signature = Signature.getInstance(provider.getOCKContext(), ockDigestAlgo, provider);
3939
} catch (Exception e) {
4040
throw provider.providerException("Failed to initialize RSA signature", e);
4141
}

src/main/java/com/ibm/crypto/plus/provider/ock/Digest.java

Lines changed: 72 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright IBM Corp. 2023, 2024
2+
* Copyright IBM Corp. 2023, 2025
33
*
44
* This code is free software; you can redistribute it and/or modify it
55
* under the terms provided by IBM in the LICENSE file that accompanied
@@ -8,6 +8,7 @@
88

99
package com.ibm.crypto.plus.provider.ock;
1010

11+
import com.ibm.crypto.plus.provider.OpenJCEPlusProvider;
1112
import java.security.AccessController;
1213
import java.security.PrivilegedAction;
1314
import java.util.concurrent.ConcurrentLinkedQueue;
@@ -29,7 +30,7 @@ public final class Digest implements Cloneable {
2930
// -2 : Not a SHA* digest algorithm
3031
private int algIndx = -1;
3132

32-
private boolean needsReinit = false;
33+
private BoolWrapper needsReinit = new BoolWrapper(false);
3334

3435
private boolean contextFromQueue = false;
3536

@@ -137,44 +138,28 @@ void getContext() throws OCKException {
137138
this.contextFromQueue = true;
138139
}
139140
}
140-
this.needsReinit = false;
141+
this.needsReinit.setValue(false);
141142
}
142143

143-
void releaseContext() throws OCKException {
144+
/* end digest caching mechanism
145+
* ===========================================================================
146+
*/
144147

145-
if (this.digestId == 0) {
146-
return;
148+
/* This wrapper is used to pass a primitive variable as a parameter by reference instead of by value to the cleaner. */
149+
public class BoolWrapper {
150+
boolean value;
151+
public BoolWrapper(boolean value) {
152+
this.value = value;
147153
}
148154

149-
// not SHA* algorithm
150-
if (this.algIndx == -2) {
151-
if (validId(this.digestId)) {
152-
NativeInterface.DIGEST_delete(this.ockContext.getId(),
153-
this.digestId);
154-
this.digestId = 0;
155-
}
156-
} else {
157-
if (this.contextFromQueue) {
158-
// reset now to make sure all contexts in the queue are ready to use
159-
this.reset();
160-
contexts[this.algIndx].add(this.digestId);
161-
this.digestId = 0;
162-
this.contextFromQueue = false;
163-
} else {
164-
// delete context
165-
if (validId(this.digestId)) {
166-
NativeInterface.DIGEST_delete(this.ockContext.getId(),
167-
this.digestId);
168-
this.digestId = 0;
169-
}
170-
}
155+
public boolean getValue(){
156+
return this.value;
171157
}
172-
this.digestId = 0;
173-
}
174158

175-
/* end digest caching mechanism
176-
* ===========================================================================
177-
*/
159+
public void setValue(boolean value) {
160+
this.value = value;
161+
}
162+
}
178163

179164
private OCKContext ockContext = null;
180165
private int digestLength = 0;
@@ -185,7 +170,9 @@ void releaseContext() throws OCKException {
185170

186171
private long digestId = 0;
187172

188-
public static Digest getInstance(OCKContext ockContext, String digestAlgo) throws OCKException {
173+
private OpenJCEPlusProvider provider;
174+
175+
public static Digest getInstance(OCKContext ockContext, String digestAlgo, OpenJCEPlusProvider provider) throws OCKException {
189176
if (ockContext == null) {
190177
throw new IllegalArgumentException("context is null");
191178
}
@@ -194,15 +181,23 @@ public static Digest getInstance(OCKContext ockContext, String digestAlgo) throw
194181
throw new IllegalArgumentException("digestAlgo is null/empty");
195182
}
196183

197-
return new Digest(ockContext, digestAlgo);
184+
return new Digest(ockContext, digestAlgo, provider);
198185
}
199186

200-
private Digest(OCKContext ockContext, String digestAlgo) throws OCKException {
187+
private Digest(OCKContext ockContext, String digestAlgo, OpenJCEPlusProvider provider) throws OCKException {
201188
//final String methodName = "Digest(String)";
202189
this.ockContext = ockContext;
203190
this.digestAlgo = digestAlgo;
191+
this.provider = provider;
204192
getContext();
205193
//OCKDebug.Msg(debPrefix, methodName, "digestAlgo :" + digestAlgo);
194+
195+
if (provider == null) {
196+
throw new IllegalArgumentException("Provider cannot be null.");
197+
}
198+
199+
this.provider.registerCleanable(this, cleanOCKResources(digestId, algIndx,
200+
contextFromQueue, needsReinit, ockContext));
206201
}
207202

208203
private Digest() {
@@ -245,7 +240,7 @@ public synchronized void update(byte[] input, int offset, int length) throws OCK
245240
if (errorCode < 0) {
246241
throwOCKException(errorCode);
247242
}
248-
this.needsReinit = true;
243+
this.needsReinit.setValue(true);
249244
}
250245

251246
public synchronized byte[] digest() throws OCKException {
@@ -267,7 +262,7 @@ public synchronized byte[] digest() throws OCKException {
267262
if (errorCode < 0) {
268263
throwOCKException(errorCode);
269264
}
270-
this.needsReinit = false;
265+
this.needsReinit.setValue(false);
271266

272267
return digestBytes;
273268
}
@@ -299,10 +294,10 @@ public synchronized void reset() throws OCKException {
299294
if (!validId(this.digestId)) {
300295
throw new OCKException(badIdMsg);
301296
}
302-
if (this.needsReinit) {
297+
if (this.needsReinit.getValue()) {
303298
NativeInterface.DIGEST_reset(this.ockContext.getId(), this.digestId);
304299
}
305-
this.needsReinit = false;
300+
this.needsReinit.setValue(false);
306301
}
307302

308303
private synchronized void obtainDigestLength() throws OCKException {
@@ -324,18 +319,6 @@ private synchronized void obtainDigestLength() throws OCKException {
324319
}
325320
}
326321

327-
@Override
328-
protected synchronized void finalize() throws Throwable {
329-
//final String methodName = "finalize";
330-
331-
try {
332-
//OCKDebug.Msg(debPrefix, methodName, "digestId =" + this.digestId);
333-
releaseContext();
334-
} finally {
335-
super.finalize();
336-
}
337-
}
338-
339322
/* At some point we may enhance this function to do other validations */
340323
protected static boolean validId(long id) {
341324
//final String methodName = "validId";
@@ -355,9 +338,10 @@ public synchronized Object clone() throws CloneNotSupportedException {
355338
copy.digestLength = this.digestLength;
356339
copy.algIndx = this.algIndx;
357340
copy.digestAlgo = new String(this.digestAlgo);
358-
copy.needsReinit = this.needsReinit;
341+
copy.needsReinit.setValue(this.needsReinit.getValue());
359342
copy.ockContext = this.ockContext;
360343
copy.contextFromQueue = false;
344+
copy.provider = this.provider;
361345

362346
// Allocate a new context for the digestId and copy all state information from our
363347
// original context into the copy.
@@ -374,6 +358,41 @@ public synchronized Object clone() throws CloneNotSupportedException {
374358
.collect(Collectors.joining("\n"));
375359
throw new CloneNotSupportedException(stackTrace);
376360
}
361+
362+
this.provider.registerCleanable(copy, cleanOCKResources(copy.digestId, copy.algIndx,
363+
copy.contextFromQueue, copy.needsReinit, copy.ockContext));
377364
return copy;
378365
}
366+
367+
private Runnable cleanOCKResources(long digestId, int algIndx, boolean contextFromQueue,
368+
BoolWrapper needsReinit, OCKContext ockContext) {
369+
return () -> {
370+
try {
371+
if (digestId == 0) {
372+
throw new OCKException("Digest Identifier is not valid");
373+
}
374+
// not SHA* algorithm
375+
if (algIndx == -2) {
376+
if (validId(digestId)) {
377+
NativeInterface.DIGEST_delete(ockContext.getId(), digestId);
378+
}
379+
} else {
380+
if (contextFromQueue) {
381+
// reset now to make sure all contexts in the queue are ready to use
382+
if (needsReinit.getValue()) {
383+
NativeInterface.DIGEST_reset(ockContext.getId(), digestId);
384+
}
385+
Digest.contexts[algIndx].add(digestId);
386+
} else {
387+
NativeInterface.DIGEST_delete(ockContext.getId(), digestId);
388+
}
389+
}
390+
} catch (OCKException e) {
391+
if (OpenJCEPlusProvider.getDebug() != null) {
392+
OpenJCEPlusProvider.getDebug().println("An error occurred while cleaning : " + e.getMessage());
393+
e.printStackTrace();
394+
}
395+
}
396+
};
397+
}
379398
}

0 commit comments

Comments
 (0)