Summary
Performance issues that can cause server lag during tab completion and resource exhaustion during long runs.
Issue 1: runBlocking on main thread in tab completion
Location:
paper/.../utils/command/resolver/RepositoryPluginParameterType.kt (lines 54-56, 76-78)
paper/.../utils/command/resolver/InstalledPluginParameterType.kt (lines 54-58, 77-81)
paper/.../utils/command/resolver/VersionSpecifierParameterType.kt (lines 67-74)
Both parse() and defaultSuggestions() use runBlocking which blocks the main server thread during tab completion. Network or disk I/O in these paths can cause visible server lag or TPS drops.
Fix: Pre-build an async cache of suggestions (refreshed periodically or on plugin state changes). Tab completion should read from memory-only cache, never from disk/network.
Issue 2: HttpClient created per-request and never closed
Location: paper/.../infrastructure/downloader/DownloaderRepositoryImpl.kt (lines 108-145, 232-245)
Each call to getLatestVersion(), getVersionByName(), getAllVersions(), and downloadByVersion() creates new instances of GithubDownloader, SpigotDownloader, ModrinthDownloader — each of which creates a new HttpClient. These clients are never closed, leaking threads and sockets.
Fix: Register a singleton HttpClient in Koin. Inject it into downloader instances. Close it in onDisable().
Issue 3: Entire JAR loaded into memory before writing to disk
Location: paper/.../infrastructure/downloader/AbstractPluginDownloader.kt (line 68)
tempFile.writeBytes(fileResponse.body())
The body() call loads the entire HTTP response into memory as a ByteArray. Large plugins (10+ MB) will cause unnecessary memory pressure.
Fix: Use bodyAsChannel() or streaming API to write directly to the temp file.
Impact
- Server lag during tab completion (noticeable to players)
- Thread/socket exhaustion over time (potential OOM or connection pool exhaustion)
- Memory spikes during plugin downloads
Summary
Performance issues that can cause server lag during tab completion and resource exhaustion during long runs.
Issue 1: runBlocking on main thread in tab completion
Location:
paper/.../utils/command/resolver/RepositoryPluginParameterType.kt(lines 54-56, 76-78)paper/.../utils/command/resolver/InstalledPluginParameterType.kt(lines 54-58, 77-81)paper/.../utils/command/resolver/VersionSpecifierParameterType.kt(lines 67-74)Both
parse()anddefaultSuggestions()userunBlockingwhich blocks the main server thread during tab completion. Network or disk I/O in these paths can cause visible server lag or TPS drops.Fix: Pre-build an async cache of suggestions (refreshed periodically or on plugin state changes). Tab completion should read from memory-only cache, never from disk/network.
Issue 2: HttpClient created per-request and never closed
Location:
paper/.../infrastructure/downloader/DownloaderRepositoryImpl.kt(lines 108-145, 232-245)Each call to
getLatestVersion(),getVersionByName(),getAllVersions(), anddownloadByVersion()creates new instances ofGithubDownloader,SpigotDownloader,ModrinthDownloader— each of which creates a newHttpClient. These clients are never closed, leaking threads and sockets.Fix: Register a singleton
HttpClientin Koin. Inject it into downloader instances. Close it inonDisable().Issue 3: Entire JAR loaded into memory before writing to disk
Location:
paper/.../infrastructure/downloader/AbstractPluginDownloader.kt(line 68)The
body()call loads the entire HTTP response into memory as aByteArray. Large plugins (10+ MB) will cause unnecessary memory pressure.Fix: Use
bodyAsChannel()or streaming API to write directly to the temp file.Impact