Skip to content

Spotless in a long-running multi-threaded JVM #1555

@blacelle

Description

@blacelle

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:

  1. com.diffplug.spotless.extra.java.EclipseJdtFormatterStep#getFormatterFunc to add the FormatterFunc.Closeable facet.
  2. com.diffplug.spotless.Jvm.Support#suggestLaterVersionOnError to transfer the optional FormatterFunc.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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions