Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Illegal reflective access by kryo #3659

Closed
juliangilbey opened this issue Jan 22, 2021 · 18 comments · Fixed by #3815
Closed

Illegal reflective access by kryo #3659

juliangilbey opened this issue Jan 22, 2021 · 18 comments · Fixed by #3815

Comments

@juliangilbey
Copy link

I'm not sure where I should report this issue, so please feel free to tell me if it should go somewhere else.

I'm running QuPath, and am opening lots of images using the BioFormats library to create thumbnail images. On one image, I get the following warning:

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.esotericsoftware.kryo.serializers.FieldSerializer (file:/home/jdg/qupath/build/dist/QuPath-0.2.3/lib/app/kryo-2.24.0.jar) to field java.util.regex.Pattern.pattern
WARNING: Please consider reporting this to the maintainers of com.esotericsoftware.kryo.serializers.FieldSerializer
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

The only use of the kryo library in QuPath is as a dependency of BioFormats. I had a look at the kryo GitHub page, and it's currently on version 6.0.1 or so; version 2.4.0 was released in 2014. So if it's a bug in kryo, it may well have been fixed by now.

I am happy to share my code, in case that helps. Unfortunately, though, this issue also seems to be transient: I repeated the extraction and did not get any warnings the second time around. But my colleague, using the same code, is also sporadically getting this same warning.

@sbesson
Copy link
Member

sbesson commented Jan 22, 2021

Hi @juliangilbey, the version of kryo has actually been bumped to 4.0.1 in the recent Bio-Formats 6.6.0 release. This might help with the warnings seen above.
In terms of QuPath, I see that is still on Bio-Formats 6.5.1. @petebankhead @mahdilamb is this something that is on your roadmap for Qupath 0.3.x? Happy to create a relevant issue wherever necessary if that helps.

@petebankhead
Copy link
Contributor

Hi @sbesson, yes indeed - we plan to always update to the latest Bio-Formats with each release. We hope 0.3.x will be available late March.

It should be possible to drag bioformats_package.jar onto QuPath to add it as an extension, although you might need to remove the existing jars within the QuPath installation to avoid them being used.

Ideally it would be possible for users to update Bio-Formats independently of QuPath in an easier way. I remain a bit torn in the best to make Bio-Formats available through QuPath - improvements to our current approach would be very welcome. See also
https://forum.image.sc/t/bio-formats-as-a-library-which-dependencies/36636

(Also @melvingelbard )

@mahdilamb
Copy link

Hi @juliangilbey, please do let us know if updating bioformats brings stability or if there are issues.

Best,
Mahdi

@juliangilbey
Copy link
Author

(Ah, correction, kryo is now at 5.0.3. My bad.)

@petebankhead @mahdilamb We'll try this suggestion, thanks. Though now I'm confused (and this is a QuPath question, not a BioFormats one): QuPath built quite happily, including qupath-extension-bioformats-0.2.3.jar, and happily says it's initialising a BioFormatsImageServer, but there is no bioformats_package.jar present at all! So where in the build process or runtime is bioformats_package.jar actually obtained and/or used?

And where would I put bioformats_package.jar (on Linux)? Would it go into build/dist/QuPath-0.2.3/lib/app/?

Thanks!
Julian

@petebankhead
Copy link
Contributor

@juliangilbey QuPath is using the 'other' way to include Bio-Formats as a library, so it is split across multiple jars (including formats-gpl.jar), for the reasons discussed in the image.sc topic. But that does make updating it more difficult.

@juliangilbey
Copy link
Author

Hi @petebankhead Super thanks! Could I just change the bioformatsVersion = '6.5.1' in build.gradle to read 6.6.0 and then rebuild QuPath? I presume that would do the job.

@petebankhead
Copy link
Contributor

@juliangilbey Yup, that should work.

(From memory, dragging the jar onto QuPath to add it as an extension may well work too - I think it does pick up the newer version, but possibly more by luck than design... you can see the version under Help → Installed extensions).

@juliangilbey
Copy link
Author

Hi @petebankhead and @mahdilamb. So I rebuilt QuPath with bioformatsVersion == '6.6.0' (I don't know how to drag a jar onto QuPath in Linux), and that built with no problems.
Unfortunately, though, I still randomly get the same illegal reflective access operation warning (though now it refers to kryo-4.0.2.jar).
This time, two images (out of the 488 I processed 5 times each, so 2 out of 2440 runs of QuPath) gave the warning, neither of which was the one that gave the original warning I reported. And of course it's not replicable, except that my colleague experienced the same thing (but again on different images). So there is clearly some randomness involved which is affecting things.
I can live with it, but it's really weird.
Thanks!

@petebankhead
Copy link
Contributor

Hello again, this resurfaces more problematically in Java 16, thanks to JEP 396: Strongly Encapsulate JDK Internals by Default.

When opening an .ndpi file with memoization enabled, I see an error:

com.esotericsoftware.kryo.KryoException: java.lang.IllegalArgumentException: Unable to create serializer "com.esotericsoftware.kryo.serializers.FieldSerializer" for class: java.util.regex.Pattern
Serialization trace:
cellomicsPattern (loci.formats.in.CellomicsReader)
readers (loci.formats.ImageReader)
reader (loci.formats.DimensionSwapper)
reader (loci.formats.FileStitcher)
helper (loci.formats.in.FilePatternReader)
readers (loci.formats.ImageReader)
        at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:101)
        at com.esotericsoftware.kryo.serializers.FieldSerializer.write(FieldSerializer.java:508)
        at com.esotericsoftware.kryo.Kryo.writeClassAndObject(Kryo.java:651)
        at com.esotericsoftware.kryo.serializers.DefaultArraySerializers$ObjectArraySerializer.write(DefaultArraySerializers.java:361)
        at com.esotericsoftware.kryo.serializers.DefaultArraySerializers$ObjectArraySerializer.write(DefaultArraySerializers.java:302)
        at com.esotericsoftware.kryo.Kryo.writeObject(Kryo.java:575)
        at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:79)
        at com.esotericsoftware.kryo.serializers.FieldSerializer.write(FieldSerializer.java:508)
        at com.esotericsoftware.kryo.Kryo.writeObject(Kryo.java:575)
        at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:79)
        at com.esotericsoftware.kryo.serializers.FieldSerializer.write(FieldSerializer.java:508)
        at com.esotericsoftware.kryo.Kryo.writeObject(Kryo.java:575)
        at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:79)
        at com.esotericsoftware.kryo.serializers.FieldSerializer.write(FieldSerializer.java:508)
        at com.esotericsoftware.kryo.Kryo.writeObject(Kryo.java:575)
        at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:79)
        at com.esotericsoftware.kryo.serializers.FieldSerializer.write(FieldSerializer.java:508)
        at com.esotericsoftware.kryo.Kryo.writeClassAndObject(Kryo.java:651)
        at com.esotericsoftware.kryo.serializers.DefaultArraySerializers$ObjectArraySerializer.write(DefaultArraySerializers.java:361)
        at com.esotericsoftware.kryo.serializers.DefaultArraySerializers$ObjectArraySerializer.write(DefaultArraySerializers.java:302)
        at com.esotericsoftware.kryo.Kryo.writeObject(Kryo.java:575)
        at com.esotericsoftware.kryo.serializers.ObjectField.write(ObjectField.java:79)
        at com.esotericsoftware.kryo.serializers.FieldSerializer.write(FieldSerializer.java:508)
        at com.esotericsoftware.kryo.Kryo.writeObject(Kryo.java:557)
        at loci.formats.Memoizer$KryoDeser.saveReader(Memoizer.java:206)
        at loci.formats.Memoizer.saveMemo(Memoizer.java:968)
        at loci.formats.Memoizer.setId(Memoizer.java:697)

Since QuPath v0.3 will use Java 16, I've been exploring workarounds.

One option that seems to work is to pass --add-opens java.base/java.util.regex=ALL-UNNAMED to the JVM, but I'd really rather avoid this in the longer term. I have a sneaking suspicion that more errors will arise, and more and more packages will need to be opened in this way.

I'm not sure where CellomicsReader fits in, but the specific error seems related to the fact it contains a Pattern object as a field.

Since it seems to be a cached value that will be replaced if null, would it be an option to simply make this transient? I assume (but don't know) kryo would skip it then and hopefully the warnings (from Java 11) and errors (from Java 16) would go away.

petebankhead added a commit to petebankhead/qupath that referenced this issue Mar 23, 2021
Fix missing Java options in console build for Windows (which meant OpenSlide couldn't be loaded).

Avoid memoization errors with --add-opens, see ome/bioformats#3659
Note that reflection may cause more problems with kryo/gson/groovy (possibly).
@sbesson
Copy link
Member

sbesson commented Mar 24, 2021

Hi @petebankhead thanks for the feedback. A few comments from our side:

  • we are only testing Bio-Formats on LTS Java versions (currently 8 and 11) and there is limited knowledge and support for other versions. Consumers are obviously free to make their own minimal requirements and we are aware QuPath is quickly adopting new Java versions.
  • feedback for Java versions above 11 is valuable anyways in the perspective of the upcoming 17 LTS release as I imagine it will give a preview some of the deprecated features
  • regarding your specific issue, the cached Pattern file stores information about the HCS layout as there are at least two naming variants. I don't see a compelling reason to serialize this information as a Pattern object. Note however, making it transient might have some performance impact when accessing Cellomics plate on the fly. Minimally, this should be tested.
  • we just released Bio-Formats 6.6.1 and are planning for a more substantial minor release that could include breaking serialization changes. As always, we will welcome external contributions and can provide assistance with testing it on our systems using our curated QA repository.

@petebankhead
Copy link
Contributor

Thanks @sbesson, I spent some hours exploring this in Bio-Formats.

I can confirm that setting the Pattern to be transient overcomes the issue I was seeing with CellomicsReader whenever I build the jar and use it directly (although I'm not entirely sure why CellomicsReader features whenever I was using the OS-2.ndpi OpenSlide file for testing) . I checked this with a simple Java class, passing the image

import java.io.IOException;

import loci.formats.FormatException;
import loci.formats.ImageReader;
import loci.formats.Memoizer;
import loci.formats.MetadataTools;
import loci.formats.meta.IMetadata;

/**
 * Simple class to confirm whether memoization causes errors in Java 16+.
 */
public class CheckForMemoizationErrors {
	
	public static void main(String[] args) {
		if ( args == null || args.length< 1 ){
			System.err.println("Name required");
			System.exit(1);
			        }
		for (String path : args) {
			try (Memoizer memoizer = new Memoizer(new ImageReader())) {
				System.out.println("Reading: " + path);
				IMetadata metadata = MetadataTools.createOMEXMLMetadata();
				memoizer.setMetadataStore(metadata);
				memoizer.setId(path);
				System.out.println("Image count: " + metadata.getImageCount());
			} catch (IOException | FormatException e) {
				e.printStackTrace();
			}
		}
	}

}

This works with a warning on Java 11, and fails with an error in Java 16.

However, I couldn't replicate it with a very easy unit test added to the existing MemoizerTest, possibly because when I run the existing Bio-Formats unit tests I see the message

2021-03-24 15:57:30,416 [main] ERROR loci.formats.ClassList - "loci.formats.in.CellomicsReader" is not valid.

Nevertheless, without me making any changes whatsoever I also saw that

mvn -Dtest=MemoizerTest -pl components/formats-bsd test

fails with Java 16. The messages are

2021-03-24 15:57:30,524 [main] ERROR loci.formats.Memoizer - deleting invalid memo file: /var/folders/9b/3jc1753j035dlgntdr26w3bc0000gn/T/loci.formats.utests.MemoizerTest.17708860208738848203/.test&pixelType=int8&sizeX=20&sizeY=20&sizeC=1&sizeZ=1&sizeT=1.fake.bfmemo
java.lang.IllegalAccessError: failed to access class loci.formats.in.JPEGReader$DefaultJPEGReader from class loci.formats.in.JPEGReader$DefaultJPEGReaderConstructorAccess (loci.formats.in.JPEGReader$DefaultJPEGReader is in unnamed module of loader 'app'; loci.formats.in.JPEGReader$DefaultJPEGReaderConstructorAccess is in unnamed module of loader com.esotericsoftware.reflectasm.AccessClassLoader @1ef6d34c)
	at loci.formats.in.JPEGReader$DefaultJPEGReaderConstructorAccess.newInstance(Unknown Source) ~[na:na]
	at com.esotericsoftware.kryo.Kryo$DefaultInstantiatorStrategy$1.newInstance(Kryo.java:1275) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.Kryo.newInstance(Kryo.java:1139) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.FieldSerializer.create(FieldSerializer.java:562) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.FieldSerializer.read(FieldSerializer.java:538) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:731) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.ObjectField.read(ObjectField.java:125) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.FieldSerializer.read(FieldSerializer.java:543) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:731) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.DefaultArraySerializers$ObjectArraySerializer.read(DefaultArraySerializers.java:391) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.DefaultArraySerializers$ObjectArraySerializer.read(DefaultArraySerializers.java:302) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:731) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.ObjectField.read(ObjectField.java:125) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.FieldSerializer.read(FieldSerializer.java:543) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:731) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.ObjectField.read(ObjectField.java:125) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.FieldSerializer.read(FieldSerializer.java:543) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:731) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.ObjectField.read(ObjectField.java:125) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.FieldSerializer.read(FieldSerializer.java:543) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:731) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.ObjectField.read(ObjectField.java:125) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.FieldSerializer.read(FieldSerializer.java:543) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:731) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.DefaultArraySerializers$ObjectArraySerializer.read(DefaultArraySerializers.java:391) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.DefaultArraySerializers$ObjectArraySerializer.read(DefaultArraySerializers.java:302) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:731) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.ObjectField.read(ObjectField.java:125) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.FieldSerializer.read(FieldSerializer.java:543) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:709) ~[kryo-4.0.2.jar:na]
	at loci.formats.Memoizer$KryoDeser.loadReader(Memoizer.java:163) ~[classes/:6.7.0-SNAPSHOT]
	at loci.formats.Memoizer.loadMemo(Memoizer.java:888) ~[classes/:6.7.0-SNAPSHOT]
	at loci.formats.Memoizer.setId(Memoizer.java:666) ~[classes/:6.7.0-SNAPSHOT]
	at loci.formats.utests.MemoizerTest.checkMemo(MemoizerTest.java:85) ~[test-classes/:na]
	at loci.formats.utests.MemoizerTest.testConstructorTimeElapsed(MemoizerTest.java:132) ~[test-classes/:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:567) ~[na:na]
	at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:80) ~[testng-6.8.jar:na]
	at org.testng.internal.Invoker.invokeMethod(Invoker.java:714) ~[testng-6.8.jar:na]
	at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:901) ~[testng-6.8.jar:na]
	at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1231) ~[testng-6.8.jar:na]
	at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127) ~[testng-6.8.jar:na]
	at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111) ~[testng-6.8.jar:na]
	at org.testng.TestRunner.privateRun(TestRunner.java:767) ~[testng-6.8.jar:na]
	at org.testng.TestRunner.run(TestRunner.java:617) ~[testng-6.8.jar:na]
	at org.testng.SuiteRunner.runTest(SuiteRunner.java:334) ~[testng-6.8.jar:na]
	at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:329) ~[testng-6.8.jar:na]
	at org.testng.SuiteRunner.privateRun(SuiteRunner.java:291) ~[testng-6.8.jar:na]
	at org.testng.SuiteRunner.run(SuiteRunner.java:240) ~[testng-6.8.jar:na]
	at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52) ~[testng-6.8.jar:na]
	at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86) ~[testng-6.8.jar:na]
	at org.testng.TestNG.runSuitesSequentially(TestNG.java:1198) ~[testng-6.8.jar:na]
	at org.testng.TestNG.runSuitesLocally(TestNG.java:1123) ~[testng-6.8.jar:na]
	at org.testng.TestNG.run(TestNG.java:1031) ~[testng-6.8.jar:na]
	at org.apache.maven.surefire.testng.TestNGExecutor.run(TestNGExecutor.java:135) ~[surefire-testng-2.22.0.jar:2.22.0]
	at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.executeSingleClass(TestNGDirectoryTestSuite.java:112) ~[surefire-testng-2.22.0.jar:2.22.0]
	at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.execute(TestNGDirectoryTestSuite.java:99) ~[surefire-testng-2.22.0.jar:2.22.0]
	at org.apache.maven.surefire.testng.TestNGProvider.invoke(TestNGProvider.java:146) ~[surefire-testng-2.22.0.jar:2.22.0]
	at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:383) ~[surefire-booter-2.22.0.jar:2.22.0]
	at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:344) ~[surefire-booter-2.22.0.jar:2.22.0]
	at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:125) ~[surefire-booter-2.22.0.jar:2.22.0]
	at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:417) ~[surefire-booter-2.22.0.jar:2.22.0]
2021-03-24 15:57:30,543 [main] WARN  loci.formats.Memoizer - skipping memo: directory not writeable - /var/folders/9b/3jc1753j035dlgntdr26w3bc0000gn/T/loci.formats.utests.MemoizerTest.1309856327206281055
2021-03-24 15:57:30,592 [main] ERROR loci.formats.Memoizer - deleting invalid memo file: /var/folders/9b/3jc1753j035dlgntdr26w3bc0000gn/T/loci.formats.utests.MemoizerTest.1309856327206281055/var/folders/9b/3jc1753j035dlgntdr26w3bc0000gn/T/loci.formats.utests.MemoizerTest.12933126901448110456/.test&pixelType=int8&sizeX=20&sizeY=20&sizeC=1&sizeZ=1&sizeT=1.fake.bfmemo
java.lang.IllegalAccessError: failed to access class loci.formats.in.JPEGReader$DefaultJPEGReader from class loci.formats.in.JPEGReader$DefaultJPEGReaderConstructorAccess (loci.formats.in.JPEGReader$DefaultJPEGReader is in unnamed module of loader 'app'; loci.formats.in.JPEGReader$DefaultJPEGReaderConstructorAccess is in unnamed module of loader com.esotericsoftware.reflectasm.AccessClassLoader @1ef6d34c)
	at loci.formats.in.JPEGReader$DefaultJPEGReaderConstructorAccess.newInstance(Unknown Source) ~[na:na]
	at com.esotericsoftware.kryo.Kryo$DefaultInstantiatorStrategy$1.newInstance(Kryo.java:1275) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.Kryo.newInstance(Kryo.java:1139) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.FieldSerializer.create(FieldSerializer.java:562) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.FieldSerializer.read(FieldSerializer.java:538) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:731) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.ObjectField.read(ObjectField.java:125) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.FieldSerializer.read(FieldSerializer.java:543) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:731) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.DefaultArraySerializers$ObjectArraySerializer.read(DefaultArraySerializers.java:391) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.DefaultArraySerializers$ObjectArraySerializer.read(DefaultArraySerializers.java:302) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:731) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.ObjectField.read(ObjectField.java:125) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.FieldSerializer.read(FieldSerializer.java:543) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:731) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.ObjectField.read(ObjectField.java:125) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.FieldSerializer.read(FieldSerializer.java:543) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:731) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.ObjectField.read(ObjectField.java:125) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.FieldSerializer.read(FieldSerializer.java:543) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:731) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.ObjectField.read(ObjectField.java:125) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.FieldSerializer.read(FieldSerializer.java:543) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:731) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.DefaultArraySerializers$ObjectArraySerializer.read(DefaultArraySerializers.java:391) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.DefaultArraySerializers$ObjectArraySerializer.read(DefaultArraySerializers.java:302) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:731) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.ObjectField.read(ObjectField.java:125) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.serializers.FieldSerializer.read(FieldSerializer.java:543) ~[kryo-4.0.2.jar:na]
	at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:709) ~[kryo-4.0.2.jar:na]
	at loci.formats.Memoizer$KryoDeser.loadReader(Memoizer.java:163) ~[classes/:6.7.0-SNAPSHOT]
	at loci.formats.Memoizer.loadMemo(Memoizer.java:888) ~[classes/:6.7.0-SNAPSHOT]
	at loci.formats.Memoizer.setId(Memoizer.java:666) ~[classes/:6.7.0-SNAPSHOT]
	at loci.formats.utests.MemoizerTest.checkMemo(MemoizerTest.java:85) ~[test-classes/:na]
	at loci.formats.utests.MemoizerTest.testConstructorTimeElapsedDirectory(MemoizerTest.java:159) ~[test-classes/:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:567) ~[na:na]
	at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:80) ~[testng-6.8.jar:na]
	at org.testng.internal.Invoker.invokeMethod(Invoker.java:714) ~[testng-6.8.jar:na]
	at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:901) ~[testng-6.8.jar:na]
	at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1231) ~[testng-6.8.jar:na]
	at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:127) ~[testng-6.8.jar:na]
	at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111) ~[testng-6.8.jar:na]
	at org.testng.TestRunner.privateRun(TestRunner.java:767) ~[testng-6.8.jar:na]
	at org.testng.TestRunner.run(TestRunner.java:617) ~[testng-6.8.jar:na]
	at org.testng.SuiteRunner.runTest(SuiteRunner.java:334) ~[testng-6.8.jar:na]
	at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:329) ~[testng-6.8.jar:na]
	at org.testng.SuiteRunner.privateRun(SuiteRunner.java:291) ~[testng-6.8.jar:na]
	at org.testng.SuiteRunner.run(SuiteRunner.java:240) ~[testng-6.8.jar:na]
	at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52) ~[testng-6.8.jar:na]
	at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86) ~[testng-6.8.jar:na]
	at org.testng.TestNG.runSuitesSequentially(TestNG.java:1198) ~[testng-6.8.jar:na]
	at org.testng.TestNG.runSuitesLocally(TestNG.java:1123) ~[testng-6.8.jar:na]
	at org.testng.TestNG.run(TestNG.java:1031) ~[testng-6.8.jar:na]
	at org.apache.maven.surefire.testng.TestNGExecutor.run(TestNGExecutor.java:135) ~[surefire-testng-2.22.0.jar:2.22.0]
	at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.executeSingleClass(TestNGDirectoryTestSuite.java:112) ~[surefire-testng-2.22.0.jar:2.22.0]
	at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.execute(TestNGDirectoryTestSuite.java:99) ~[surefire-testng-2.22.0.jar:2.22.0]
	at org.apache.maven.surefire.testng.TestNGProvider.invoke(TestNGProvider.java:146) ~[surefire-testng-2.22.0.jar:2.22.0]
	at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:383) ~[surefire-booter-2.22.0.jar:2.22.0]
	at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:344) ~[surefire-booter-2.22.0.jar:2.22.0]
	at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:125) ~[surefire-booter-2.22.0.jar:2.22.0]
	at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:417) ~[surefire-booter-2.22.0.jar:2.22.0]
2021-03-24 15:57:30,607 [main] WARN  loci.formats.Memoizer - skipping memo: directory not writeable - /var/folders/9b/3jc1753j035dlgntdr26w3bc0000gn/T/loci.formats.utests.MemoizerTest.13351549293586754205
2021-03-24 15:57:30,609 [main] WARN  loci.formats.Memoizer - skipping memo: directory not writeable - /var/folders/9b/3jc1753j035dlgntdr26w3bc0000gn/T/loci.formats.utests.MemoizerTest.4924479803228739786
2021-03-24 15:57:30,611 [main] WARN  loci.formats.Memoizer - skipping memo: directory not writeable - /var/folders/9b/3jc1753j035dlgntdr26w3bc0000gn/T/loci.formats.utests.MemoizerTest.12210169080269039602
[ERROR] Tests run: 12, Failures: 2, Errors: 0, Skipped: 0, Time elapsed: 0.989 s <<< FAILURE! - in loci.formats.utests.MemoizerTest
[ERROR] testConstructorTimeElapsed(loci.formats.utests.MemoizerTest)  Time elapsed: 0.141 s  <<< FAILURE!
java.lang.AssertionError: expected [true] but found [false]
	at loci.formats.utests.MemoizerTest.checkMemo(MemoizerTest.java:86)
	at loci.formats.utests.MemoizerTest.testConstructorTimeElapsed(MemoizerTest.java:132)

[ERROR] testConstructorTimeElapsedDirectory(loci.formats.utests.MemoizerTest)  Time elapsed: 0.065 s  <<< FAILURE!
java.lang.AssertionError: expected [true] but found [false]
	at loci.formats.utests.MemoizerTest.checkMemo(MemoizerTest.java:86)
	at loci.formats.utests.MemoizerTest.testConstructorTimeElapsedDirectory(MemoizerTest.java:159)

[INFO] 
[INFO] Results:
[INFO] 
[ERROR] Failures: 
[ERROR]   MemoizerTest.testConstructorTimeElapsed:132->checkMemo:86 expected [true] but found [false]
[ERROR]   MemoizerTest.testConstructorTimeElapsedDirectory:159->checkMemo:86 expected [true] but found [false]
[INFO] 
[ERROR] Tests run: 12, Failures: 2, Errors: 0, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  5.618 s
[INFO] Finished at: 2021-03-24T15:57:31Z
[INFO] ------------------------------------------------------------------------

The cause of the problem seems to be the same. The easy workaround in that case is to make DefaultJPEGReader public but I suspect that isn't desirable. It is fine in Java 11.

I can submit a pull request to set pattern to transient, but since I don't have a meaningful test for it I suspect that probably doesn't help much. I'm afraid I won't have time to delve any deeper in the next month or so.

Regarding QuPath quickly adopting new Java versions, this is because Java 11 doesn't have jpackage - and we need that for creating our installers. We've resisted using other new features so our code should be compatible with Java 11. We skipped Java 15 because too much had changed, but we are aiming for Java 16 so we're ready for 17 when it comes later this year... and hopefully we can stick with LTS releases afterwards.

@imagesc-bot
Copy link

This issue has been mentioned on Image.sc Forum. There might be relevant details there:

https://forum.image.sc/t/stardist-qupath-3-0-snaphot-detection-for-bigger-annotations/51978/2

@tlambert03
Copy link

just chiming in to say that I see this as well using bioformats 6.7.0, in python via jpype.

❯ java --version
openjdk 11.0.9.1 2020-11-04 LTS
OpenJDK Runtime Environment Zulu11.43+55-CA (build 11.0.9.1+1-LTS)
OpenJDK 64-Bit Server VM Zulu11.43+55-CA (build 11.0.9.1+1-LTS, mixed mode)
import jpype
jpype.startJVM(classpath='path/to/bioformats_package.jar')
loci = jpype.JPackage("loci")
loci.common.DebugTools.setRootLevel("ERROR")
r = loci.formats.Memoizer(loci.formats.ImageReader())
r.setId('whatever.tif')
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.esotericsoftware.kryo.util.UnsafeUtil (file:bioformats_package.jar) to constructor java.nio.DirectByteBuffer(long,int,java.lang.Object)
WARNING: Please consider reporting this to the maintainers of com.esotericsoftware.kryo.util.UnsafeUtil
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

@petebankhead
Copy link
Contributor

I am experiencing this again in Java 17 (LTS) - except I haven't been able to find a way past it because the previous workaround of --illegal-access=permit is no longer possible due to JEP 396.

Using --add-opens java.base/java.util.regex=ALL-UNNAMED still gets me past the first problem, but I don't know how to resolve

java.lang.IllegalAccessError: failed to access class loci.formats.in.JPEGReader$DefaultJPEGReader from class loci.formats.in.JPEGReader$DefaultJPEGReaderConstructorAccess (loci.formats.in.JPEGReader$DefaultJPEGReader is in unnamed module of loader 'app'; loci.formats.in.JPEGReader$DefaultJPEGReaderConstructorAccess is in unnamed module of loader com.esotericsoftware.reflectasm.AccessClassLoader @68241ff8)
	at loci.formats.in.JPEGReader$DefaultJPEGReaderConstructorAccess.newInstance(Unknown Source)
	at com.esotericsoftware.kryo.Kryo$DefaultInstantiatorStrategy$1.newInstance(Kryo.java:1275)
	at com.esotericsoftware.kryo.Kryo.newInstance(Kryo.java:1139)

Without solving this, I don't think it's possible to use memoization on Java 17.

@sbesson
Copy link
Member

sbesson commented Apr 21, 2022

@petebankhead I just opened #3815 which includes a minimal change to the inner DefaultJPEGReader class that seems to fix the unit tests on Java 17 previously failing with the IllegalAccessError.

The change should be tested in our CI tomorrow also including #3796 which fixes the other known Java 17 memoization issue. Would be interested to hear if that fixes your use case as well.
Both of these changes are memo-breaking so they could not be included in the Bio-Formats 6.9.1 patch release which just went out but I would expect they will be available in the follow-up minor release and allow the serialization to become functional on Java 17 environments.

@petebankhead
Copy link
Contributor

Thanks @sbesson that sounds good.

Would it be possible to also incorporate the change making Pattern in CellomicsReader become transient, as described at #3659 (comment) ?

Without that, there are still Java 17 problems unless the application is launched with --add-opens java.base/java.util.regex=ALL-UNNAMED - which I imagine many people won't do (or won't want to do).

@sbesson
Copy link
Member

sbesson commented Apr 21, 2022

By incorporating, do you mean merging #3815 and #3796 into a single PR to simplify the review?
If so, no objection to doing so from my side but I'll consult with @melissalinkert when she is online.

@petebankhead
Copy link
Contributor

Sorry, ignore me, I read it too quickly and missed that both are covered!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants