Skip to content

Commit

Permalink
Merge pull request guardian#4197 from guardian/reaper-cron-like-schedule
Browse files Browse the repository at this point in the history
  • Loading branch information
twrichards authored Nov 27, 2023
2 parents 50aaa74 + a145fba commit 7f9bf83
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.gu.mediaservice.lib

import java.time.format.DateTimeFormatter
import java.time.{Instant, ZoneId, ZonedDateTime}

import java.time.{Instant, LocalDateTime, ZoneId, ZonedDateTime}
import org.joda.time.DateTime

import java.time.temporal.ChronoUnit
import scala.concurrent.duration.{DurationLong, FiniteDuration}
import scala.util.Try

object DateTimeUtils {
Expand All @@ -18,4 +19,14 @@ object DateTimeUtils {

// TODO move this to a LocalDateTime
def fromValueOrNow(value: Option[String]): DateTime = Try{new DateTime(value.get)}.getOrElse(DateTime.now)

def timeUntilNextInterval(interval: FiniteDuration, now: ZonedDateTime = now): FiniteDuration = {
val nowRoundedDownToTheHour = now.truncatedTo(ChronoUnit.HOURS)
val millisSinceTheHour = ChronoUnit.MILLIS.between(nowRoundedDownToTheHour, now).toDouble
val numberOfIntervals = (millisSinceTheHour / interval.toMillis).ceil.toLong
ChronoUnit.MILLIS.between(
now,
nowRoundedDownToTheHour plusSeconds (interval mul numberOfIntervals).toSeconds
).millis
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import org.joda.time.DateTime
import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers

import java.time.ZonedDateTime
import java.time.temporal.ChronoUnit
import scala.concurrent.duration.{DurationInt, DurationLong, FiniteDuration}


class DateTimeUtilsTest extends AnyFunSpec with Matchers {
it ("should convert a string to a DateTime") {
val dateString = "2020-01-01T12:34:56.000Z"
Expand All @@ -21,4 +26,28 @@ class DateTimeUtilsTest extends AnyFunSpec with Matchers {
val actual = DateTimeUtils.fromValueOrNow(None)
actual shouldBe a[DateTime]
}

it ("should return the time until the next instance of the interval relative to the hour"){
def toZonedDateTime(timePart: String) = ZonedDateTime.parse(s"2023-11-21T${timePart}Z[Europe/London]")
def test(nowTime: String, expectedTime: String, interval: FiniteDuration = 15.minutes) = {
DateTimeUtils.timeUntilNextInterval(
interval,
toZonedDateTime(nowTime)
) shouldEqual ChronoUnit.MILLIS.between(
toZonedDateTime(nowTime),
toZonedDateTime(expectedTime)
).millis
}
test(nowTime = "11:11:23.887", expectedTime = "11:15:00.000")
test(nowTime = "11:23:23.887", expectedTime = "11:30:00.000")
test(nowTime = "11:33:23.887", expectedTime = "11:45:00.000")
test(nowTime = "11:50:23.887", expectedTime = "12:00:00.000")
test(nowTime = "11:00:00.000", expectedTime = "11:00:00.000")
test(nowTime = "11:00:00.001", expectedTime = "11:15:00.000")

test(nowTime = "11:00:00.001", expectedTime = "11:01:00.000", interval = 1.minute)
test(nowTime = "11:00:00.001", expectedTime = "11:02:00.000", interval = 2.minute)

test(nowTime = "11:01:00.001", expectedTime = "12:00:00.000", interval = 1.hour)
}
}
6 changes: 3 additions & 3 deletions thrall/app/controllers/ReaperController.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package controllers

import akka.actor.Scheduler
import com.gu.mediaservice.lib.ImageIngestOperations
import com.gu.mediaservice.lib.{DateTimeUtils, ImageIngestOperations}
import com.gu.mediaservice.lib.auth.Permissions.DeleteImage
import com.gu.mediaservice.lib.auth.{Authentication, Authorisation, BaseControllerWithLoginRedirects}
import com.gu.mediaservice.lib.config.Services
Expand All @@ -15,7 +15,7 @@ import org.joda.time.{DateTime, DateTimeZone}
import play.api.libs.json.{JsValue, Json}
import play.api.mvc.{Action, AnyContent, ControllerComponents}

import scala.concurrent.duration.DurationInt
import java.time.temporal.ChronoUnit
import scala.concurrent.{ExecutionContext, Future}
import scala.jdk.CollectionConverters.collectionAsScalaIterableConverter
import scala.language.postfixOps
Expand Down Expand Up @@ -50,7 +50,7 @@ class ReaperController(
(config.maybeReaperBucket, config.maybeReaperCountPerRun) match {
case (Some(reaperBucket), Some(countOfImagesToReap)) =>
scheduler.scheduleAtFixedRate(
initialDelay = 0 seconds,
initialDelay = DateTimeUtils.timeUntilNextInterval(INTERVAL), // so we always start on multiples of the interval past the hour
interval = INTERVAL,
){ () =>
if(store.client.doesObjectExist(reaperBucket, CONTROL_FILE_NAME)) {
Expand Down

0 comments on commit 7f9bf83

Please sign in to comment.