33import com .diffplug .common .base .Either ;
44import com .diffplug .common .base .Errors ;
55import com .diffplug .common .base .Throwables ;
6+ import com .diffplug .common .base .Throwing ;
67import com .google .common .collect .ImmutableList ;
78import com .google .common .io .ByteStreams ;
89import com .google .common .io .Files ;
1819import org .gradle .api .GradleException ;
1920import org .postgresql .ds .PGSimpleDataSource ;
2021import webtools .Env ;
21- import webtools .SetupCleanup ;
2222
2323import java .io .*;
2424import java .nio .charset .StandardCharsets ;
@@ -58,7 +58,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
5858 return FileVisitResult .CONTINUE ;
5959 }
6060 });
61- new Impl (). start (keyFile (projectDir ), this );
61+ startImpl (keyFile (projectDir ), this );
6262 } catch (Exception e ) {
6363 var rootCause = Throwables .getRootCause (e );
6464 if (rootCause != null && rootCause .getMessage () != null ) {
@@ -70,8 +70,43 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
7070 }
7171 }
7272
73+ private void startImpl (File keyFile , SetupCleanupDockerFlyway key ) throws Exception {
74+ synchronized (key .getClass ()) {
75+ byte [] required = toBytes (key );
76+ if (keyFile .exists ()) {
77+ byte [] actual = java .nio .file .Files .readAllBytes (keyFile .toPath ());
78+ if (java .util .Arrays .equals (actual , required )) {
79+ // short-circuit if our state is already setup
80+ return ;
81+ } else {
82+ java .nio .file .Files .delete (keyFile .toPath ());
83+ @ SuppressWarnings ("unchecked" )
84+ SetupCleanupDockerFlyway lastKey = (SetupCleanupDockerFlyway ) fromBytes (required );
85+ new Impl ().doStop (lastKey );
86+ }
87+ }
88+ // write out the key
89+ new Impl ().doStart (key );
90+ java .nio .file .Files .createDirectories (keyFile .toPath ().getParent ());
91+ java .nio .file .Files .write (keyFile .toPath (), required );
92+ }
93+ }
94+
7395 void forceStop (File projectDir ) throws Exception {
74- new Impl ().forceStop (keyFile (projectDir ), this );
96+ try {
97+ new Impl ().doStop (this );
98+ } catch (Exception e ) {
99+ if (Throwables .getStackTraceAsString (e ).contains ("Connection refused" )) {
100+ // if we can't connect to docker, then we can't stop it
101+ // so we'll just ignore the error
102+ } else {
103+ e .printStackTrace ();
104+ }
105+ }
106+ File kf = keyFile (projectDir );
107+ if (kf .exists ()) {
108+ java .nio .file .Files .delete (kf .toPath ());
109+ }
75110 }
76111
77112 PGSimpleDataSource getConnection () throws IOException {
@@ -105,6 +140,49 @@ private static File keyFile(File projectDir) {
105140 return new File (projectDir , "build/docker" );
106141 }
107142
143+ private static final int TRY_SILENTLY_FOR = 10_000 ;
144+ private static final int TRY_LOUDLY_UNTIL = 12_000 ;
145+ private static final int WAIT_BETWEEN_TRIES = 100 ;
146+
147+ public static void keepTrying (Throwing .Runnable toAttempt ) {
148+ long start = System .currentTimeMillis ();
149+ while (true ) {
150+ try {
151+ toAttempt .run ();
152+ return ;
153+ } catch (Throwable e ) {
154+ long elapsed = System .currentTimeMillis () - start ;
155+ if (elapsed < TRY_SILENTLY_FOR ) {
156+ Errors .rethrow ().run (() -> Thread .sleep (WAIT_BETWEEN_TRIES ));
157+ } else if (elapsed < TRY_LOUDLY_UNTIL ) {
158+ e .printStackTrace ();
159+ Errors .rethrow ().run (() -> Thread .sleep (WAIT_BETWEEN_TRIES ));
160+ } else {
161+ throw Errors .asRuntime (e );
162+ }
163+ }
164+ }
165+ }
166+
167+ private static byte [] toBytes (Object key ) {
168+ ByteArrayOutputStream bytes = new ByteArrayOutputStream ();
169+ try (ObjectOutputStream objectOutput = new ObjectOutputStream (bytes )) {
170+ objectOutput .writeObject (key );
171+ } catch (IOException e ) {
172+ throw new RuntimeException (e );
173+ }
174+ return bytes .toByteArray ();
175+ }
176+
177+ private static Object fromBytes (byte [] raw ) throws ClassNotFoundException {
178+ ByteArrayInputStream bytes = new ByteArrayInputStream (raw );
179+ try (ObjectInputStream objectOutput = new ObjectInputStream (bytes )) {
180+ return objectOutput .readObject ();
181+ } catch (IOException e ) {
182+ throw new RuntimeException (e );
183+ }
184+ }
185+
108186 DockerComposeRule rule () {
109187 return DockerComposeRule .builder ()
110188 .file (dockerComposeFile .getAbsolutePath ())
@@ -117,8 +195,7 @@ DockerComposeRule rule() {
117195 .build ();
118196 }
119197
120- private static class Impl extends SetupCleanup <SetupCleanupDockerFlyway > {
121- @ Override
198+ private static class Impl {
122199 protected void doStart (SetupCleanupDockerFlyway key ) throws IOException , InterruptedException {
123200 DockerComposeRule rule ;
124201 String ip ;
@@ -145,7 +222,7 @@ protected void doStart(SetupCleanupDockerFlyway key) throws IOException, Interru
145222
146223 // run flyway
147224 PGSimpleDataSource postgres = key .getConnection ();
148- SetupCleanup . keepTrying (() -> {
225+ keepTrying (() -> {
149226 Flyway .configure ()
150227 .dataSource (postgres )
151228 .locations ("filesystem:" + key .flywayMigrations .getAbsolutePath ())
@@ -178,7 +255,6 @@ protected void doStart(SetupCleanupDockerFlyway key) throws IOException, Interru
178255 Files .write (schema , key .flywaySchemaDump , StandardCharsets .UTF_8 );
179256 }
180257
181- @ Override
182258 protected void doStop (SetupCleanupDockerFlyway key ) throws IOException , InterruptedException {
183259 if (!Env .isGitHubAction ()) {
184260 DockerCompose compose = key .rule ().dockerCompose ();
0 commit comments