Projects and Java fixes: 2026.2 platform, JDK setup, Gradle migration, Check-button deadlock - #62
Merged
Merged
Conversation
ActionManager.tryToExecute(..., now = true) does not just run an action: it runs a full action update session and blocks the EDT in runBlockingForActionExpand waiting for it. For an ActionUpdateThread.BGT action that update needs a read action, which deadlocks against a background VFS refresh that holds the write lock and is itself waiting to be serviced on the EDT. The cycle has no timeout, so the IDE has to be killed. Clicking Check shortly after the course files are saved on disk is enough to lose the race. CheckPanelButtonComponent decides button enablement itself, and all three actions it hosts (CheckAction, NextTaskAction, RetryAction) no-op their update() for the CheckPanel place, so the update session computes nothing we use. Skip it and call ActionUtil.performAction directly, which is what the platform's own AnActionLink does. The two course-selection call sites do rely on the action's own update(), so they keep tryToExecute and instead pass now = false. That routes through tryToExecuteSuspend, which updates via expandActionGroupSuspend without blocking the EDT, still honours presentation.isEnabled, and still performs the action on the EDT. doValidation is chained off the returned ActionCallback via invokeLater with the dialog's ModalityState, since that callback can be resolved on a background thread and doValidation touches Swing. Fixes #61 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Continues the
bug/project-and-javawork that #60 started, and adds a fix for the freeze reported in #61.Four commits, 85 files. The last commit is the deadlock fix and is described in full below; the three
projects and java fixescommits predate it and are summarised by theme.Fix the permanent EDT deadlock on the Check button (#61)
ActionManager.tryToExecute(action, inputEvent, component, place, now = true)does not simply run an action. Withnow = trueit takes this path:So it runs a full action update session and blocks the EDT waiting for it. For an
ActionUpdateThread.BGTaction that update needs a read action while the EDT is parked, which closes a cycle against a background VFS refresh:runBlockingForActionExpandtryToExecute(…, now = true))InternalReadAction.readLoopThere is no timeout, so the IDE has to be killed. The reporter confirmed this with two
jstacksamples 100 s apart showing bit-identical CPU counters on the write-lock holder. Clicking Check shortly after the course files are saved on disk is enough to lose the race.Two shapes of fix, chosen per call site
CheckPanelButtonComponent— drop the update session. The panel already decides enablement (it passesisEnabledin and only attaches the listener when true), and all three actions it hosts —CheckAction,NextTaskAction,RetryAction— no-op theirupdate()for theCheckPanelplace. The update session computed nothing we used. It now builds the event directly and callsActionUtil.performAction, which is exactly what the platform's ownAnActionLinkdoes — the same panel's "Peek solution" link already took that path without freezing.The two course-selection sites — keep the update, move it off the EDT.
SwitchTaskPanelAction.updategenuinely disables itself (project != null && project.isEduProject() || ACTION_SEARCH == place), so skipping the update there would have silently changed behaviour. Those sites keeptryToExecuteand passnow = false, which routes throughtryToExecuteSuspend: it updates viaUtils.expandActionGroupSuspend(suspending, never blocks the EDT), still honourspresentation.isEnabled, and still performs the action on the EDT viarunOnEdtWithConditionalWriteIntentSuspending.invokeSwitchUILibraryrandoValidationon the next line, relying ontryToExecutebeing synchronous, so that is now chained off the returnedActionCallback. It usesdoWhenProcessedrather thandoWhenDonebecausedoValidationcurrently runs even when the action is rejected, and it marshals throughinvokeLaterwith a capturedModalityStatebecause the reject path resolves the callback on a background thread whiledoValidationtouches Swing, and the course dialog is modal.Notes for review
ActionUtil.performAction,AnActionEvent.createEvent(6-arg),ActionUiKind,ActionCallback.doWhenProcessedandModalityState.stateForComponentall have byte-identical signatures on 252 / 253 / 261 / 262 and none is@ApiStatus.Internal, so nobranches/<version>split was needed.performActionand notinvokeAction: theinvokeActionoverloads are a deprecation chain ending atperformAction. These are Kotlin@Deprecated, invisible tojavap— only compiling reveals them.actionPerformedon the EDT is still safe:FileDocumentManager.saveAllDocuments()early-returns before taking any lock when nothing is unsaved, and otherwise wraps the save in aPotemkinProgresswhoseEventStealer.isUrgentInvocationEventexplicitly whitelistsInternalThreading$TransferredWriteActionEvent, so a pending transferred write action does get dispatched.createHyperlinkWithContextHelpinnewproject/ui/utils.ktis dead code —CoursesPanel.toolbarAction()returns null and is never overridden, so noToolbarActionWrapperis ever constructed. It was converted for consistency; deleting it might be the better cleanup.CheckPanelButtonComponent), and the underlying race was not reproduced locally. The claim supported here is that the lock cycle is structurally broken, not that the freeze is confirmed gone in the wild.:intellij-plugin:hs-core:compileKotlinis clean.Earlier commits on this branch
Summarised from the diff rather than from having authored or reviewed them — worth a closer look from whoever wrote them.
environmentNamebump,libs.versions.tomlandgradle-252/253.propertiesupdates,intellij-plugin-common-conventions.gradle.ktsandintellijUtils.ktchanges, plus newTestOnlyThreadingCompatandRefreshQueueCompatshims under all fourbranches/<version>trees.JdkAutoInstaller.kt,ProjectJdkRepair.ktand aJdkDownloadUi.javashim; a large rewrite ofJdkLanguageSettings.kt; changes toJdkProjectSettings,JdkEnvironmentSettingsandParsedJavaVersion;SqlJdkLanguageSettingsfolded away. Covered by the newJdkSelectionTest.GradleScriptMigration.kt, slimmedGradleStartupActivity,EduGradleUtilschanges and a new settings-script template, withGradleAdditionalFilesMigrationTestandGradleSettingsScriptMigrationTest.EduNode,DirectoryNode,LessonNode,SectionNode,FrameworkLessonNode,CCSectionNodeandCourseViewUtils, withNodesTestextended.YamlLoader,YamlDeepLoader,YamlFormatSynchronizer,StudyItemChangeApplier,StudyItem,ItemContainerand the*Updaterclasses, with a newItemContainerTestand an addedHyperskillSectionUpdateTestcase.HyperskillCheckConnector,HyperskillConnector,StepikAPIandRetrofitExt, with a newHyperskillRejectedEduTaskSubmissionTest.JCEFToolWindow,TabManager,DescriptionTab,CodeHighlighter,HintsWrapper.🤖 Generated with Claude Code