Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Amortize slow LFF startup time #1788

Merged
merged 8 commits into from
May 28, 2023
Merged

Amortize slow LFF startup time #1788

merged 8 commits into from
May 28, 2023

Conversation

petervdonovan
Copy link
Collaborator

@petervdonovan petervdonovan commented May 26, 2023

Fixes #1342.

Benefits of correctly incorporating LFF into Spotless include:

  • uniform framework for formatting tests in this repository, including spotlessCheck, for which we have not implemented an analogue
  • use of Spotless's PaddedCell features
  • use of Spotless's ability to run only on changed files (I have verified that using the Spotless task is empirically much faster when only one file has been modified since the last formatter run)

This solution works by giving the formatter the ability to listen in its input stream for the paths that it needs to format. This lets us use one instance of the formatter for the entire lifetime of the calling Gradle daemon.

To my knowledge, Spotless does not provide an API for releasing resources used by the formatting step across multiple invocations of the formatter. However the JVM does provide a shutdown hook, which I think is an acceptable though imperfect way to release the formatter. I have verified on Ubuntu that the formatter subprocess does indeed terminate when the enclosing Gradle daemon is killed with a SIGKILL.

private static Writer writer;

private static BufferedReader reader;
private static BufferedReader error;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a little bit of a red flag that these variables are static. It is necessary because Step needs to be Serializable. I do not fully understand why this is required by Spotless, but I am under the impression that it relates to an optimization that we are not trying to use at the moment.

@petervdonovan petervdonovan requested a review from cmnrd May 26, 2023 21:05
@petervdonovan petervdonovan force-pushed the slow-lff branch 2 times, most recently from 83afd7f to c142bf4 Compare May 26, 2023 23:09
@cmnrd cmnrd changed the base branch from master to gradle May 27, 2023 09:13
Copy link
Collaborator

@cmnrd cmnrd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! I changed the base to the gradle branch. It's probably best to merge it there first.

There is one major issue with this though. As lff runs as a daemon in the background, any changes we make to the formatter do not have an effect until we kill the gradle daemon and then do a clean build. Can we just spawn a new lff process when create is called and kill the old one?

@cmnrd
Copy link
Collaborator

cmnrd commented May 27, 2023

Since you are the first to work with the new repo structure in #1779. Do you think it makes sense?

@petervdonovan
Copy link
Collaborator Author

Do you think it makes sense?

Yes, I think so. One minor issue that I see is that RunSingleTestMain and RuntimeTest are separated from everything else that is in the org.lflang.tests package (why?), and for some reason IntelliJ does not seem to know how to build those two files (code help does not work for them).

@petervdonovan
Copy link
Collaborator Author

petervdonovan commented May 27, 2023

On line 26, where it says

spotlessLinguaFranca.dependsOn(":org.lflang:cli:lff:installDist")

There seems to be a circular dependency, for running ./gradlew org.lflang:cli:lff:installDist triggers an error

* Where:
Build file '/home/peter/vscode-lingua-franca/lingua-franca/build.gradle' line: 19

* What went wrong:
A problem occurred configuring root project 'org.lflang'.
> Failed to notify project evaluation listener.
   > Cannot run program "org.lflang/cli/lff/build/install/lff/bin/lff": error=2, No such file or directory

which must mean that we ran spotlessLinguaFranca in the process of running installDist, before installDist had produced its output, lff.

EDIT: RESOLVED. See commit message of fe8dfb9 for details.

This terminates the spawned formatter by closing its input stream. The
formatter should detect that the stream is closed and understand that it
will not receive any more files to format and exit gracefully. This
prevents the formatter from outliving its Gradle daemon.

If the daemon is terminated forcefully e.g. with a -9 SIGTERM rather
than a -15 SIGKILL, then this will not work. Otherwise I believe that
this should release the resource (get rid of the spawned formatter)
correctly.
The problem was not that Gradle runs any spotlessLinguaFranca tasks as
part of its other operations, but that it configures the task before
starting any other operation (including clean). The solution is to start
the formatter lazily -- to wait until spotlessLinguaFranca actually
operates on a file before trying to start up the formatter process.
@petervdonovan petervdonovan marked this pull request as ready for review May 27, 2023 19:39
@petervdonovan
Copy link
Collaborator Author

Can we just spawn a new lff process when create is called and kill the old one?

Yes, good idea. Now we always run terminateFormatter when create is called and we initialize the formatter as needed.

@petervdonovan petervdonovan requested a review from cmnrd May 27, 2023 19:44
@cmnrd
Copy link
Collaborator

cmnrd commented May 28, 2023

Yes, I think so. One minor issue that I see is that RunSingleTestMain and RuntimeTest are separated from everything else that is in the org.lflang.tests package (why?), and for some reason IntelliJ does not seem to know how to build those two files (code help does not work for them).

This is just an artifact of the porting process. The plan is to eliminate org.lflang.tests and move all its contents. The only thing remaining to be ported is RunSingleTestMain.

This ensures, that gradle recognizes a change in lff and reformats all lf files
accordingly.
Copy link
Collaborator

@cmnrd cmnrd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great! I think the static variables and the fact that we need to run a separate lff daemon, are just artifacts of the hacky thing we as doing, where a build flow (formatting) also depends on our build output (lff). It seems to work fine though!

I made a small change and added the outputs of the task that insalls lff to the inputs of our formatting tasks. This ensures that gradle automatically reformats all lf files when lff changed.

@cmnrd cmnrd merged commit 29b754d into gradle May 28, 2023
@cmnrd cmnrd deleted the slow-lff branch May 28, 2023 12:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

LFFormatter is slow
2 participants