Skip to content

Commit 0dd3cf3

Browse files
committed
fix server I think
1 parent a762850 commit 0dd3cf3

File tree

3 files changed

+100
-50
lines changed

3 files changed

+100
-50
lines changed

Dockerfile

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,21 @@ RUN wget https://raw.githubusercontent.com/VirtusLab/scala-cli/main/scala-cli.sh
99
scala-cli version && \
1010
echo '@main def hello = println(42)' | scala-cli run _ --js -S 3.5.0
1111

12+
WORKDIR /scratch
13+
14+
COPY backend/project.scala .
15+
16+
RUN scala-cli compile project.scala
17+
18+
COPY frontend/src/project.scala .
19+
20+
RUN scala-cli compile project.scala
21+
22+
WORKDIR /source/frontend
23+
COPY frontend/package.json .
24+
COPY frontend/package-lock.json .
25+
RUN npm install
26+
1227
WORKDIR /source
1328

1429
COPY . .
@@ -21,9 +36,9 @@ RUN npm install
2136
RUN npm run build
2237

2338
WORKDIR /source/backend
24-
RUN scala-cli package . --assembly -f -o ./optimizer-backend
39+
RUN scala-cli package . --assembly -f -o ./optimizer-backend --offline --server=false
2540

26-
FROM eclipse-temurin:22
41+
FROM ghcr.io/graalvm/jdk-community:23
2742

2843
COPY --from=build /source/backend/optimizer-backend /app/optimizer-backend
2944

backend/server.scala

Lines changed: 81 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import java.util.concurrent.TimeUnit
1212
import scala.concurrent.ExecutionContext
1313
import scala.concurrent.Future
1414
import scala.jdk.CollectionConverters.*
15+
import scala.util.control.NonFatal
16+
import scala.util.Try
17+
18+
import scala.concurrent.ExecutionContext.global as GlobalEC
1519

1620
object Limits:
1721
lazy val MaxFileLength =
@@ -38,41 +42,47 @@ object Optimizer extends cask.MainRoutes:
3842
def assets() = "assets"
3943

4044
@cask.websocket("/ws/connect/:id")
41-
def showUserProfile(id: String): cask.WebsocketResult =
42-
val j = parseJobId(id)
43-
cask.WsHandler { channel =>
45+
def jobUpdates(id: String): cask.WebsocketResult =
46+
val jobId = parseJobId(id)
47+
val handler = cask.WsHandler { channel =>
4448
var sc: Option[ScheduledFuture[?]] = None
4549

4650
val actor = cask.WsActor {
51+
case cask.Ws.Ping(dt) =>
52+
cask.Ws.Pong(dt)
4753

4854
case cask.Ws.Text("ping") =>
49-
heartbeatJob(j) match
55+
heartbeatJob(jobId) match
5056
case None =>
51-
sc.map(_.cancel(true))
57+
sc.foreach(_.cancel(true))
5258
channel.send(cask.Ws.Close())
5359

5460
case Some(value) if value.instruction == TrainingInstruction.Halt =>
55-
sc.map(_.cancel(true))
61+
sc.foreach(_.cancel(true))
5662
channel.send(cask.Ws.Close())
57-
5863
case _ =>
64+
cask.Ws.Pong()
65+
5966
case cask.Ws.Close(_, _) =>
60-
haltJob(j, "websocket connection closed")
61-
sc.map(_.cancel(true))
67+
haltJob(jobId, "websocket connection closed")
68+
sc.foreach(_.cancel(true))
69+
6270
}
6371

6472
sc = Some(
6573
scheduler.scheduleAtFixedRate(
66-
run(sendJobUpdate(j, channel)),
67-
0,
74+
run(sendJobUpdate(jobId, channel, () => sc.foreach(_.cancel(true)))),
75+
1,
6876
1,
6977
TimeUnit.SECONDS
7078
)
7179
)
7280

7381
actor
7482
}
75-
end showUserProfile
83+
84+
if jobs.containsKey(jobId) then handler else cask.Response("", 404)
85+
end jobUpdates
7686

7787
@cask.post("/api/halt/:id")
7888
def doThing(id: String) =
@@ -94,7 +104,7 @@ object Optimizer extends cask.MainRoutes:
94104
else if attrs.generations > Limits.MaxGenerations then
95105
error(s"Number of generations above maximum [${Limits.MaxGenerations}]")
96106
else
97-
val (id, job) = createJob(attrs)(using executionContext)
107+
val (id, job) = createJob(attrs, GlobalEC)
98108
cask.Response(id.toString(), 200)
99109
end if
100110
end doThing
@@ -163,40 +173,64 @@ case class Result(
163173
generations: Int
164174
) derives ReadWriter
165175

166-
def sendJobUpdate(id: UUID, channel: cask.WsChannelActor) = () =>
167-
Option(jobs.get(id))
168-
.foreach: job =>
169-
job.result.foreach: result =>
170-
val serialised =
171-
Conf.printHocon(result.config.toScalafmt(base))
172-
173-
val serialisedBase =
174-
Conf.printHocon(base)
175-
176-
val codec = ScalafmtConfig.encoder
177-
178-
val diff = munit.diff
179-
.Diff(job.attributes.file, result.formattedFile)
180-
.unifiedDiff
181-
182-
val configDiff =
183-
Conf.printHocon(
184-
Conf.patch(
185-
codec.write(base),
186-
codec.write(result.config.toScalafmt(base))
176+
inline def absorb[T](msg: String, f: => T) =
177+
try f
178+
catch
179+
case NonFatal(exc) =>
180+
scribe.error(s"Failed to [$msg]", exc)
181+
182+
def sendJobUpdate(id: UUID, channel: cask.WsChannelActor, cancel: () => Unit) =
183+
() =>
184+
Option(jobs.get(id)) match
185+
case None =>
186+
absorb(
187+
s"closing WS connection for [$id]",
188+
channel.send(cask.Ws.Close())
189+
)
190+
cancel()
191+
case Some(job) if job.instruction == TrainingInstruction.Halt =>
192+
absorb(
193+
s"closing WS connection for [$id]",
194+
channel.send(cask.Ws.Close())
195+
)
196+
cancel()
197+
case Some(job) =>
198+
channel.send(cask.Ws.Ping())
199+
job.result.foreach: result =>
200+
val serialised =
201+
Conf.printHocon(result.config.toScalafmt(base))
202+
203+
val serialisedBase =
204+
Conf.printHocon(base)
205+
206+
val codec = ScalafmtConfig.encoder
207+
208+
val diff = munit.diff
209+
.Diff(job.attributes.file, result.formattedFile)
210+
.unifiedDiff
211+
212+
val configDiff =
213+
Conf.printHocon(
214+
Conf.patch(
215+
codec.write(base),
216+
codec.write(result.config.toScalafmt(base))
217+
)
187218
)
188-
)
189219

190-
val res = Result(
191-
config = serialised,
192-
formattedFile = result.formattedFile,
193-
fileDiff = diff,
194-
configDiff = configDiff,
195-
generation = result.generation,
196-
generations = job.attributes.generations
197-
)
220+
val res = Result(
221+
config = serialised,
222+
formattedFile = result.formattedFile,
223+
fileDiff = diff,
224+
configDiff = configDiff,
225+
generation = result.generation,
226+
generations = job.attributes.generations
227+
)
198228

199-
channel.send(cask.Ws.Text(upickle.default.write(res)))
229+
absorb(
230+
s"sending update for [$id]",
231+
channel.send(cask.Ws.Text(upickle.default.write(res)))
232+
)
233+
end match
200234

201235
def haltJob(id: UUID, reason: String | Null = null) =
202236
Option(
@@ -220,7 +254,7 @@ case class TrainingResult(
220254
generation: Int
221255
)
222256

223-
def createJob(attrs: JobAttributes)(using ExecutionContext): (UUID, Job) =
257+
def createJob(attrs: JobAttributes, ec: ExecutionContext): (UUID, Job) =
224258
val id = generateJobId()
225259

226260
val job = Job(
@@ -277,15 +311,15 @@ def createJob(attrs: JobAttributes)(using ExecutionContext): (UUID, Job) =
277311
end handle
278312
end handler
279313

280-
Future:
314+
ec.execute: () =>
281315
Train(
282316
featureful = summon[Featureful[ScalafmtConfigSubset]],
283317
config = trainingConfig,
284318
fitness = cachedFitness(trainingConfig.random, 500)(
285319
Fitness(fitness(attrs.file, _))
286320
),
287321
events = handler,
288-
evaluator = ParallelCollectionsEvaluator
322+
evaluator = SequentialEvaluator
289323
).train()
290324

291325
id -> job

frontend/src/main.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import com.raquo.laminar.api.L.*
22
import io.laminext.websocket.WebSocket
33

44
import upickle.default.ReadWriter
5+
import com.raquo.airstream.core.Signal
56

67
case class Result(
78
config: String,
@@ -42,7 +43,7 @@ case class JobAttributes(
4243

4344
val error = Var(Option.empty[String])
4445

45-
val updateJob =
46+
val updateJob: Signal[Option[Result]] =
4647
id.signal.flatMapSwitch:
4748
case None => Signal.fromValue(None)
4849
case Some(id) =>

0 commit comments

Comments
 (0)