-
Notifications
You must be signed in to change notification settings - Fork 487
Description
Here, I consider Spotless use through CleanThat
First, we try to process files in parallel. It lead to issues with some FormatterFunc which are not thread-safe. Typical example:
- EclipseJdtFormatterStep: in the end, it relies on a single
org.eclipse.jdt.core.formatter.CodeFormatter
which is not thread-safe.
Then, we considered instantiating Spotless formatters on a per-thread basis. However, it lead to Metaspace issues (java.lang.OutOfMemoryError: Metaspace
). This is quite normal as we end duplicating each Class (e.g. the whole Eclipse JDT eco-system) for each working thread.
While investigating this Metaspace issue, we spot that Eclipse starts a bunch of Thread (for each ClassLoader), hence remaining in RAM, hence maintaining a reference to the whole JarState.
Typical stacks:
1:
InternalWorker(Thread).<init>(ThreadGroup, Runnable, String, long, AccessControlContext, boolean) line: 396
InternalWorker(Thread).<init>(ThreadGroup, Runnable, String, long) line: 708
InternalWorker(Thread).<init>(String) line: 541
InternalWorker.<init>(JobManager) line: 31
JobManager.<init>() line: 297
JobManager.getInstance() line: 223
InternalJob.<clinit>() line: 71
WorkManager.<init>(Workspace) line: 95
Workspace.startup(IProgressMonitor) line: 2462
Workspace.open(IProgressMonitor) line: 2231
ResourcesPlugin.start(BundleContext) line: 475
BundleController.addBundle(int, BundleActivator, Function<Bundle,BundleException>) line: 116
SpotlessEclipseFramework.addPlugin(int, BundleActivator) line: 276
SpotlessEclipseFramework.setup(SpotlessEclipseConfig) line: 245
EclipseJdtFormatterStepImpl.<init>(Properties) line: 40
2:
Worker(Thread).<init>(ThreadGroup, Runnable, String, long, AccessControlContext, boolean) line: 396
Worker(Thread).<init>(ThreadGroup, Runnable, String, long) line: 708
Worker(Thread).<init>(String) line: 541
Worker.<init>(WorkerPool) line: 34
WorkerPool.jobQueued() line: 155
JobManager.schedule(InternalJob, long, boolean) line: 1320
StringPoolJob(InternalJob).schedule(long) line: 385
StringPoolJob(Job).schedule(long) line: 684
StringPoolJob.addStringPoolParticipant(IStringPoolParticipant, ISchedulingRule) line: 67
Workspace.open(IProgressMonitor) line: 2245
ResourcesPlugin.start(BundleContext) line: 475
BundleController.addBundle(int, BundleActivator, Function<Bundle,BundleException>) line: 116
SpotlessEclipseFramework.addPlugin(int, BundleActivator) line: 276
SpotlessEclipseFramework.setup(SpotlessEclipseConfig) line: 245
EclipseJdtFormatterStepImpl.<init>(Properties) line: 40
3:
Worker(Thread).<init>(ThreadGroup, Runnable, String, long, AccessControlContext, boolean) line: 396
Worker(Thread).<init>(ThreadGroup, Runnable, String, long) line: 708
Worker(Thread).<init>(String) line: 541
Worker.<init>(WorkerPool) line: 34
WorkerPool.jobQueued() line: 155
WorkerPool.startJob(Worker) line: 269
Worker.run() line: 58
We considered calling FormatterStepImpl.Standard
-> FormatterFunc.Closeable
(e.g. to call org.eclipse.core.internal.jobs.JobManager.shutdown()
). However, it seems not implemented in the EclipseJdt case.
We understand it could be fixed through:
com.diffplug.spotless.extra.java.EclipseJdtFormatterStep#getFormatterFunc
to add theFormatterFunc.Closeable
facet.com.diffplug.spotless.Jvm.Support#suggestLaterVersionOnError
to transfer the optionalFormatterFunc.Closeable
facet.
Is our approach correct? Do you have recommendations to rely on Spotless on a multithread+long-running environnement?
We may rather work in making each formatter thread-safe (which may not be achievable/quite difficult with some formatters (e.g. Prettier ?)) ?
Should we rather consider being able to share a JarState through multiple instance of the same formatterFunction ?
Thanks