Skip to content
Snippets Groups Projects
Commit 2057178c authored by Clément Pit-Claudel's avatar Clément Pit-Claudel
Browse files

server/gc: Reuse Clock implementation

parent bc2610a4
No related branches found
No related tags found
1 merge request!31server: Add a GarbageCollection
package cs214.webapp
package server
package web
import java.util.concurrent.{TimeUnit, ScheduledExecutorService, ScheduledThreadPoolExecutor, ScheduledFuture}
import scala.util.Try
private final class Clock(val name: String, periodMs: Long)(using scheduler: ScheduledExecutorService)(action: => Unit):
private var handler: Option[ScheduledFuture[?]] = None
val tick = new Runnable:
def run() = Try(action) // Ignore errors (`action` should catch them)
def start(): Unit =
scheduler.synchronized:
require(handler.isEmpty)
handler = Some(scheduler.scheduleWithFixedDelay(tick, 0, periodMs, TimeUnit.MILLISECONDS))
println(f"[$name] started")
def shutdown() =
scheduler.synchronized:
handler.foreach(_.cancel(true))
handler = None
println(f"[$name] stopped")
object Clock:
def newScheduler(): ScheduledThreadPoolExecutor =
val scheduler = ScheduledThreadPoolExecutor(1)
scheduler.setRemoveOnCancelPolicy(true)
scheduler
...@@ -2,17 +2,15 @@ package cs214.webapp ...@@ -2,17 +2,15 @@ package cs214.webapp
package server.web package server.web
import scala.collection.concurrent import scala.collection.concurrent
import scala.concurrent.duration.Duration import scala.concurrent.duration.*
import java.util.concurrent.TimeUnit
object GarbageCollector: object GarbageCollector:
/// When to do GC? private val GC_INTERVAL = 1.minutes
private val GC_EVERY = Duration(1, TimeUnit.MINUTES)
/// Forces GC after this number of unused instances /// Forces GC after this number of unused instances
private val GC_AFTER_UNUSED_INSTANCES = 10 private val GC_AFTER_UNUSED_INSTANCES = 10
/// GC an instance after this unused time. /// GC an instance after this unused time.
private val GC_AFTER_DURATION = Duration(24, TimeUnit.HOURS) private val GC_AFTER_DURATION = 24.hours
type LastUsedAt = Long type LastUsedAt = Long
/** Contains the list of instances that are not used */ /** Contains the list of instances that are not used */
...@@ -31,7 +29,7 @@ object GarbageCollector: ...@@ -31,7 +29,7 @@ object GarbageCollector:
WebServer.shutdownApp(i) WebServer.shutdownApp(i)
/** Garbage collect instances */ /** Garbage collect instances */
def garbageCollect = def garbageCollect(): Unit =
val snap = scheduledForDeletion.snapshot() val snap = scheduledForDeletion.snapshot()
// Conditions to trigger garbage collection // Conditions to trigger garbage collection
if snap.size >= GC_AFTER_UNUSED_INSTANCES then if snap.size >= GC_AFTER_UNUSED_INSTANCES then
...@@ -46,25 +44,8 @@ object GarbageCollector: ...@@ -46,25 +44,8 @@ object GarbageCollector:
println(f"Stopping instance $i after $GC_AFTER_DURATION inactivity.") println(f"Stopping instance $i after $GC_AFTER_DURATION inactivity.")
shutdownApp(i) shutdownApp(i)
/** The thread running the garbage collection */ private var clock =
class Deleter extends Thread("gc"): Clock("gc", GC_INTERVAL.toMillis)(using Clock.newScheduler())(garbageCollect())
override def run(): Unit =
println("GC started.")
try
while true do
Thread.sleep(GC_EVERY.toMillis)
garbageCollect
catch
case e: InterruptedException => println("GC stopped.")
private var gcThread: Option[Thread] = None
/** Starts the garbage collector thread */
def start() =
gcThread = Some(Deleter())
gcThread.foreach(_.start())
/** Stops the garbage collection thread */ def start() = clock.start()
def shutdown() = def shutdown() = clock.shutdown()
gcThread.map(_.interrupt())
gcThread = None
...@@ -2,7 +2,7 @@ package cs214.webapp ...@@ -2,7 +2,7 @@ package cs214.webapp
package server package server
package web package web
import java.util.concurrent.{Executors, TimeUnit, ScheduledExecutorService, ScheduledThreadPoolExecutor, ScheduledFuture} import java.util.concurrent.ScheduledExecutorService
import scala.collection.mutable import scala.collection.mutable
import scala.util.{Success, Failure, Try} import scala.util.{Success, Failure, Try}
...@@ -28,9 +28,7 @@ private[web] case class StateMachineServerAppFactory[E, S, V](sm: StateMachine[E ...@@ -28,9 +28,7 @@ private[web] case class StateMachineServerAppFactory[E, S, V](sm: StateMachine[E
private[web] case class ClockDrivenStateMachineServerAppFactory[E, S, V](sm: ClockDrivenStateMachine[E, S, V]) extends ServerAppFactory: private[web] case class ClockDrivenStateMachineServerAppFactory[E, S, V](sm: ClockDrivenStateMachine[E, S, V]) extends ServerAppFactory:
// Use one scheduler thread per app, not per app instance // Use one scheduler thread per app, not per app instance
val scheduler = ScheduledThreadPoolExecutor(1) val scheduler = Clock.newScheduler()
scheduler.setRemoveOnCancelPolicy(true)
override def appInfo: AppInfo = sm.appInfo override def appInfo: AppInfo = sm.appInfo
override def init(instanceId: InstanceId, registeredUserIds: Seq[UserId]): ServerApp = override def init(instanceId: InstanceId, registeredUserIds: Seq[UserId]): ServerApp =
ClockDrivenStateMachineServerApp(sm, instanceId, registeredUserIds)(using scheduler) ClockDrivenStateMachineServerApp(sm, instanceId, registeredUserIds)(using scheduler)
...@@ -169,24 +167,6 @@ private[web] class StateMachineServerApp[E, S, V]( ...@@ -169,24 +167,6 @@ private[web] class StateMachineServerApp[E, S, V](
pending pending
private final class Clock(val name: String, periodMs: Long)(using scheduler: ScheduledExecutorService)(action: => Unit):
private var handler: Option[ScheduledFuture[?]] = None
val tick = new Runnable:
def run() = Try(action) // Ignore errors (`action` should catch them)
def start(): Unit =
scheduler.synchronized:
require(handler.isEmpty)
handler = Some(scheduler.scheduleWithFixedDelay(tick, 0, periodMs, TimeUnit.MILLISECONDS))
println(f"[$name] started")
def shutdown() =
scheduler.synchronized:
handler.foreach(_.cancel(true))
handler = None
println(f"[$name] stopped")
private[web] final class ClockDrivenStateMachineServerApp[E, S, V]( private[web] final class ClockDrivenStateMachineServerApp[E, S, V](
sm: ClockDrivenStateMachine[E, S, V], sm: ClockDrivenStateMachine[E, S, V],
instanceId: InstanceId, instanceId: InstanceId,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment