Skip to content

IllegalStateException: Terminal has been closed when exiting Shell application from within the IDE #1363

Description

@mp911de

When running a Spring Shell application through the IDE and stopping the programm through the process kill button, then in almost all cases, the exit signal causes an exception:

java.lang.IllegalStateException: Terminal has been closed
	at org.jline.terminal.impl.AbstractTerminal.checkClosed(AbstractTerminal.java:143)
	at org.jline.terminal.impl.DumbTerminal.setAttributes(DumbTerminal.java:206)
	at org.jline.reader.impl.LineReaderImpl.readLine(LineReaderImpl.java:810)
	at org.jline.reader.impl.LineReaderImpl.readLine(LineReaderImpl.java:535)
	at org.springframework.shell.jline.JLineInputProvider.readInput(JLineInputProvider.java:28)
	at org.springframework.shell.core.InteractiveShellRunner.doRun(InteractiveShellRunner.java:94)
	at org.springframework.shell.core.InteractiveShellRunner.run(InteractiveShellRunner.java:83)
	at org.springframework.data.release.cli.ShellConfiguration.lambda$springShellApplicationRunner$1(ShellConfiguration.java:148)
	at org.springframework.boot.SpringApplication.lambda$callRunner$0(SpringApplication.java:788)
	at org.springframework.util.function.ThrowingConsumer$1.acceptWithException(ThrowingConsumer.java:82)
	at org.springframework.util.function.ThrowingConsumer.accept(ThrowingConsumer.java:60)
	at org.springframework.util.function.ThrowingConsumer$1.accept(ThrowingConsumer.java:86)
	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:800)
	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:788)
	at org.springframework.boot.SpringApplication.lambda$callRunners$0(SpringApplication.java:776)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
	at java.base/java.util.stream.SortedOps$SizedRefSortingSink.end(SortedOps.java:357)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:557)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:546)
	at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:265)
	at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:611)
	at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:776)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:328)
	at org.springframework.data.release.Application.main(Application.java:51)

The issue surfaces only sometimes and upon application exit, it seems not to cause any harm, however, it would be neat to suppress the exception logging for this particular case. There is also another instance from JLineShellRunner that we tried to handle in our config code.

Version: 4.0.3

Config:

@Configuration(proxyBeanMethods = false)
public class ShellConfiguration {

	@Bean
	static BeanPostProcessor shellPostProcessor() {
		return new BeanPostProcessor() {
			@Override
			public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
				if (bean instanceof InteractiveShellRunner runner) {
					runner.setDebugMode(true);
				}

				return bean;
			}
		};
	}

	@Bean
	@Primary
	@ConditionalOnProperty(prefix = "spring.shell.interactive", name = "enabled", havingValue = "true",
			matchIfMissing = true)
	public ShellRunner myJlineShellRunner(JLineInputProvider inputProvider, AbstractTerminal terminal,
			CommandParser commandParser, CommandRegistry commandRegistry, Environment environment) {
		JLineShellRunner jLineShellRunner = new JLineShellRunner(inputProvider, commandParser, commandRegistry) {

			// reduce mount of exceptions on close.
			@Override
			public void flush() {
				try {
					super.flush();
				} catch (Exception e) {
					try {
						terminal.input();
					} catch (IllegalStateException ex) {
						if (ex.getMessage() != null && ex.getMessage().contains("closed")) {
							stop();
						}
					}
				}
			}
		};
		Boolean debugMode = environment.getProperty("spring.shell.debug.enabled", Boolean.class, false);
		jLineShellRunner.setDebugMode(debugMode);
		return jLineShellRunner;
	}

	@Bean
	@ConditionalOnProperty(prefix = "spring.shell.interactive", name = "enabled", havingValue = "true",
			matchIfMissing = true)
	ApplicationRunner springShellApplicationRunner(ObjectProvider<InfoProperties> infoProperties,
			Environment environment, ObjectProvider<ShellRunner> shellRunner) {

		ResourceBanner banner = new ReleaseCliBanner(infoProperties);
		return args -> {

			PrintStream out = System.out;
			banner.printBanner(environment, getClass(), out);
			out.flush();

			ShellRunner r = shellRunner.getObject();
			if (r != null) {
				r.run(args.getSourceArgs());
			}
		};
	}

}

Metadata

Metadata

Assignees

No one assigned

    Labels

    status/need-triageTeam needs to triage and take a first look

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions