Move job queue to scheduler-api and fix notification of periodic tasks

This commit is contained in:
eikek
2022-03-12 15:31:27 +01:00
parent aafd908906
commit 83d3644b39
31 changed files with 108 additions and 103 deletions

View File

@ -530,7 +530,7 @@ val schedulerApi = project
Dependencies.fs2Core ++ Dependencies.fs2Core ++
Dependencies.circeCore Dependencies.circeCore
) )
.dependsOn(loggingApi, common, store, pubsubApi) .dependsOn(loggingApi, common, store, notificationApi, pubsubApi)
val schedulerImpl = project val schedulerImpl = project
.in(file("modules/scheduler/impl")) .in(file("modules/scheduler/impl"))

View File

@ -7,19 +7,17 @@
package docspell.backend package docspell.backend
import cats.effect._ import cats.effect._
import docspell.backend.auth.Login import docspell.backend.auth.Login
import docspell.backend.fulltext.CreateIndex import docspell.backend.fulltext.CreateIndex
import docspell.backend.msg.JobQueuePublish
import docspell.backend.ops._ import docspell.backend.ops._
import docspell.backend.signup.OSignup import docspell.backend.signup.OSignup
import docspell.ftsclient.FtsClient import docspell.ftsclient.FtsClient
import docspell.notification.api.{EventExchange, NotificationModule} import docspell.notification.api.{EventExchange, NotificationModule}
import docspell.pubsub.api.PubSubT import docspell.pubsub.api.PubSubT
import docspell.scheduler.msg.JobQueuePublish
import docspell.store.Store import docspell.store.Store
import docspell.store.usertask.UserTaskStore import docspell.store.usertask.UserTaskStore
import docspell.totp.Totp import docspell.totp.Totp
import emil.Emil import emil.Emil
trait BackendApp[F[_]] { trait BackendApp[F[_]] {

View File

@ -7,17 +7,19 @@
package docspell.backend.msg package docspell.backend.msg
import cats.data.NonEmptyList import cats.data.NonEmptyList
import docspell.pubsub.api.{Topic, TypedTopic} import docspell.pubsub.api.TypedTopic
import docspell.scheduler.msg.JobDone import docspell.scheduler.msg._
/** All topics used in Docspell. */ /** All topics used in Docspell. */
object Topics { object Topics {
/** A generic notification to the job executors to look for new work. */
val jobsNotify: TypedTopic[Unit] =
TypedTopic[Unit](Topic("jobs-notify"))
/** A list of all topics. It is required to list every topic in use here! */ /** A list of all topics. It is required to list every topic in use here! */
val all: NonEmptyList[TypedTopic[_]] = val all: NonEmptyList[TypedTopic[_]] =
NonEmptyList.of(JobDone.topic, CancelJob.topic, jobsNotify, JobSubmitted.topic) NonEmptyList.of(
JobDone.topic,
CancelJob.topic,
JobsNotify(),
JobSubmitted.topic,
PeriodicTaskNotify()
)
} }

View File

@ -9,19 +9,17 @@ package docspell.backend.ops
import cats.effect.{Async, Resource} import cats.effect.{Async, Resource}
import cats.implicits._ import cats.implicits._
import fs2.Stream import fs2.Stream
import docspell.backend.JobFactory import docspell.backend.JobFactory
import docspell.backend.PasswordCrypt import docspell.backend.PasswordCrypt
import docspell.backend.ops.OCollective._ import docspell.backend.ops.OCollective._
import docspell.common._ import docspell.common._
import docspell.store.UpdateResult import docspell.store.UpdateResult
import docspell.store.queries.{QCollective, QUser} import docspell.store.queries.{QCollective, QUser}
import docspell.store.queue.JobQueue
import docspell.store.records._ import docspell.store.records._
import docspell.store.usertask.{UserTask, UserTaskScope, UserTaskStore} import docspell.store.usertask.{UserTask, UserTaskScope, UserTaskStore}
import docspell.store.{AddResult, Store} import docspell.store.{AddResult, Store}
import com.github.eikek.calev._ import com.github.eikek.calev._
import docspell.scheduler.JobQueue
trait OCollective[F[_]] { trait OCollective[F[_]] {

View File

@ -9,14 +9,12 @@ package docspell.backend.ops
import cats.data.OptionT import cats.data.OptionT
import cats.effect._ import cats.effect._
import cats.implicits._ import cats.implicits._
import docspell.backend.JobFactory import docspell.backend.JobFactory
import docspell.backend.ops.OFileRepository.IntegrityResult import docspell.backend.ops.OFileRepository.IntegrityResult
import docspell.common._ import docspell.common._
import docspell.scheduler.JobQueue
import docspell.store.Store import docspell.store.Store
import docspell.store.queue.JobQueue
import docspell.store.records.RJob import docspell.store.records.RJob
import scodec.bits.ByteVector import scodec.bits.ByteVector
trait OFileRepository[F[_]] { trait OFileRepository[F[_]] {

View File

@ -10,15 +10,14 @@ import cats.data.NonEmptyList
import cats.effect._ import cats.effect._
import cats.implicits._ import cats.implicits._
import fs2.Stream import fs2.Stream
import docspell.backend.JobFactory import docspell.backend.JobFactory
import docspell.backend.ops.OItemSearch._ import docspell.backend.ops.OItemSearch._
import docspell.common._ import docspell.common._
import docspell.ftsclient._ import docspell.ftsclient._
import docspell.query.ItemQuery._ import docspell.query.ItemQuery._
import docspell.query.ItemQueryDsl._ import docspell.query.ItemQueryDsl._
import docspell.scheduler.JobQueue
import docspell.store.queries.{QFolder, QItem, SelectedItem} import docspell.store.queries.{QFolder, QItem, SelectedItem}
import docspell.store.queue.JobQueue
import docspell.store.records.RJob import docspell.store.records.RJob
import docspell.store.{Store, qb} import docspell.store.{Store, qb}

View File

@ -9,7 +9,6 @@ package docspell.backend.ops
import cats.data.{NonEmptyList => Nel, OptionT} import cats.data.{NonEmptyList => Nel, OptionT}
import cats.effect.{Async, Resource} import cats.effect.{Async, Resource}
import cats.implicits._ import cats.implicits._
import docspell.backend.AttachedEvent import docspell.backend.AttachedEvent
import docspell.backend.JobFactory import docspell.backend.JobFactory
import docspell.backend.fulltext.CreateIndex import docspell.backend.fulltext.CreateIndex
@ -18,11 +17,10 @@ import docspell.common._
import docspell.ftsclient.FtsClient import docspell.ftsclient.FtsClient
import docspell.logging.Logger import docspell.logging.Logger
import docspell.notification.api.Event import docspell.notification.api.Event
import docspell.scheduler.JobQueue
import docspell.store.queries.{QAttachment, QItem, QMoveAttachment} import docspell.store.queries.{QAttachment, QItem, QMoveAttachment}
import docspell.store.queue.JobQueue
import docspell.store.records._ import docspell.store.records._
import docspell.store.{AddResult, Store, UpdateResult} import docspell.store.{AddResult, Store, UpdateResult}
import doobie.implicits._ import doobie.implicits._
trait OItem[F[_]] { trait OItem[F[_]] {

View File

@ -9,11 +9,10 @@ package docspell.backend.ops
import cats.data.OptionT import cats.data.OptionT
import cats.effect._ import cats.effect._
import cats.implicits._ import cats.implicits._
import docspell.backend.msg.JobDone
import docspell.backend.ops.OJob.{CollectiveQueueState, JobCancelResult} import docspell.backend.ops.OJob.{CollectiveQueueState, JobCancelResult}
import docspell.common._ import docspell.common._
import docspell.pubsub.api.PubSubT import docspell.pubsub.api.PubSubT
import docspell.scheduler.msg.JobDone
import docspell.store.Store import docspell.store.Store
import docspell.store.UpdateResult import docspell.store.UpdateResult
import docspell.store.queries.QJob import docspell.store.queries.QJob

View File

@ -9,15 +9,16 @@ package docspell.backend.ops
import cats.Applicative import cats.Applicative
import cats.effect._ import cats.effect._
import cats.implicits._ import cats.implicits._
import docspell.backend.msg.{CancelJob, Topics}
import docspell.common.Ident import docspell.common.Ident
import docspell.pubsub.api.PubSubT import docspell.pubsub.api.PubSubT
import docspell.scheduler.msg.{CancelJob, JobsNotify, PeriodicTaskNotify}
trait OJoex[F[_]] { trait OJoex[F[_]] {
def notifyAllNodes: F[Unit] def notifyAllNodes: F[Unit]
def notifyPeriodicTasks: F[Unit]
def cancelJob(job: Ident, worker: Ident): F[Unit] def cancelJob(job: Ident, worker: Ident): F[Unit]
} }
@ -26,7 +27,10 @@ object OJoex {
Resource.pure[F, OJoex[F]](new OJoex[F] { Resource.pure[F, OJoex[F]](new OJoex[F] {
def notifyAllNodes: F[Unit] = def notifyAllNodes: F[Unit] =
pubSub.publish1IgnoreErrors(Topics.jobsNotify, ()).as(()) pubSub.publish1IgnoreErrors(JobsNotify(), ()).void
def notifyPeriodicTasks: F[Unit] =
pubSub.publish1IgnoreErrors(PeriodicTaskNotify(), ()).void
def cancelJob(job: Ident, worker: Ident): F[Unit] = def cancelJob(job: Ident, worker: Ident): F[Unit] =
pubSub.publish1IgnoreErrors(CancelJob.topic, CancelJob(job, worker)).as(()) pubSub.publish1IgnoreErrors(CancelJob.topic, CancelJob(job, worker)).as(())

View File

@ -11,11 +11,10 @@ import cats.data.{EitherT, OptionT}
import cats.effect._ import cats.effect._
import cats.implicits._ import cats.implicits._
import fs2.Stream import fs2.Stream
import docspell.backend.JobFactory import docspell.backend.JobFactory
import docspell.common._ import docspell.common._
import docspell.scheduler.JobQueue
import docspell.store.Store import docspell.store.Store
import docspell.store.queue.JobQueue
import docspell.store.records._ import docspell.store.records._
trait OUpload[F[_]] { trait OUpload[F[_]] {

View File

@ -10,14 +10,12 @@ import cats.data.{NonEmptyList, OptionT}
import cats.effect._ import cats.effect._
import cats.implicits._ import cats.implicits._
import fs2.Stream import fs2.Stream
import docspell.common._ import docspell.common._
import docspell.notification.api.{ChannelRef, PeriodicDueItemsArgs, PeriodicQueryArgs} import docspell.notification.api.{ChannelRef, PeriodicDueItemsArgs, PeriodicQueryArgs}
import docspell.scheduler.JobQueue
import docspell.store.Store import docspell.store.Store
import docspell.store.queue.JobQueue
import docspell.store.records.RNotificationChannel import docspell.store.records.RNotificationChannel
import docspell.store.usertask._ import docspell.store.usertask._
import io.circe.Encoder import io.circe.Encoder
trait OUserTask[F[_]] { trait OUserTask[F[_]] {
@ -98,7 +96,7 @@ object OUserTask {
ptask <- task.encode.toPeriodicTask(scope, subject) ptask <- task.encode.toPeriodicTask(scope, subject)
job <- ptask.toJob job <- ptask.toJob
_ <- queue.insert(job) _ <- queue.insert(job)
_ <- joex.notifyAllNodes _ <- joex.notifyPeriodicTasks
} yield () } yield ()
def getScanMailbox(scope: UserTaskScope): Stream[F, UserTask[ScanMailboxArgs]] = def getScanMailbox(scope: UserTaskScope): Stream[F, UserTask[ScanMailboxArgs]] =
@ -124,7 +122,7 @@ object OUserTask {
): F[Unit] = ): F[Unit] =
for { for {
_ <- taskStore.updateTask[ScanMailboxArgs](scope, subject, task) _ <- taskStore.updateTask[ScanMailboxArgs](scope, subject, task)
_ <- joex.notifyAllNodes _ <- joex.notifyPeriodicTasks
} yield () } yield ()
def getNotifyDueItems( def getNotifyDueItems(
@ -153,7 +151,7 @@ object OUserTask {
): F[Unit] = ): F[Unit] =
for { for {
_ <- taskStore.updateTask[PeriodicDueItemsArgs](scope, subject, task) _ <- taskStore.updateTask[PeriodicDueItemsArgs](scope, subject, task)
_ <- joex.notifyAllNodes _ <- joex.notifyPeriodicTasks
} yield () } yield ()
def getPeriodicQuery(scope: UserTaskScope): Stream[F, UserTask[PeriodicQueryArgs]] = def getPeriodicQuery(scope: UserTaskScope): Stream[F, UserTask[PeriodicQueryArgs]] =
@ -180,7 +178,7 @@ object OUserTask {
): F[Unit] = ): F[Unit] =
for { for {
_ <- taskStore.updateTask[PeriodicQueryArgs](scope, subject, task) _ <- taskStore.updateTask[PeriodicQueryArgs](scope, subject, task)
_ <- joex.notifyAllNodes _ <- joex.notifyPeriodicTasks
} yield () } yield ()
// When retrieving arguments containing channel references, we must update // When retrieving arguments containing channel references, we must update

View File

@ -12,7 +12,6 @@ import fs2.concurrent.SignallingRef
import docspell.analysis.TextAnalyser import docspell.analysis.TextAnalyser
import docspell.backend.MailAddressCodec import docspell.backend.MailAddressCodec
import docspell.backend.fulltext.CreateIndex import docspell.backend.fulltext.CreateIndex
import docspell.backend.msg.{CancelJob, JobQueuePublish, Topics}
import docspell.backend.ops._ import docspell.backend.ops._
import docspell.common._ import docspell.common._
import docspell.ftsclient.FtsClient import docspell.ftsclient.FtsClient
@ -32,13 +31,17 @@ import docspell.joex.process.ItemHandler
import docspell.joex.process.ReProcessItem import docspell.joex.process.ReProcessItem
import docspell.joex.scanmailbox._ import docspell.joex.scanmailbox._
import docspell.scheduler._ import docspell.scheduler._
import docspell.scheduler.impl.{PeriodicSchedulerBuilder, SchedulerBuilder} import docspell.scheduler.impl.{
PeriodicSchedulerBuilder,
PeriodicTaskStore,
SchedulerBuilder
}
import docspell.joex.updatecheck._ import docspell.joex.updatecheck._
import docspell.notification.api.NotificationModule import docspell.notification.api.NotificationModule
import docspell.notification.impl.NotificationModuleImpl import docspell.notification.impl.NotificationModuleImpl
import docspell.pubsub.api.{PubSub, PubSubT} import docspell.pubsub.api.{PubSub, PubSubT}
import docspell.scheduler.msg.JobQueuePublish
import docspell.store.Store import docspell.store.Store
import docspell.store.queue._
import docspell.store.records.{REmptyTrashSetting, RJobLog} import docspell.store.records.{REmptyTrashSetting, RJobLog}
import docspell.store.usertask.UserTaskScope import docspell.store.usertask.UserTaskScope
import docspell.store.usertask.UserTaskStore import docspell.store.usertask.UserTaskStore
@ -49,7 +52,6 @@ final class JoexAppImpl[F[_]: Async](
cfg: Config, cfg: Config,
store: Store[F], store: Store[F],
queue: JobQueue[F], queue: JobQueue[F],
pubSubT: PubSubT[F],
pstore: PeriodicTaskStore[F], pstore: PeriodicTaskStore[F],
termSignal: SignallingRef[F, Boolean], termSignal: SignallingRef[F, Boolean],
notificationMod: NotificationModule[F], notificationMod: NotificationModule[F],
@ -67,20 +69,11 @@ final class JoexAppImpl[F[_]: Async](
_ <- Async[F].start(eventConsume) _ <- Async[F].start(eventConsume)
_ <- scheduler.periodicAwake _ <- scheduler.periodicAwake
_ <- periodicScheduler.periodicAwake _ <- periodicScheduler.periodicAwake
_ <- subscriptions _ <- scheduler.startSubscriptions
_ <- periodicScheduler.startSubscriptions
} yield () } yield ()
} }
def subscriptions =
for {
_ <- Async[F].start(pubSubT.subscribeSink(Topics.jobsNotify) { _ =>
scheduler.notifyChange
})
_ <- Async[F].start(pubSubT.subscribeSink(CancelJob.topic) { msg =>
scheduler.requestCancel(msg.body.jobId).as(())
})
} yield ()
def findLogs(jobId: Ident): F[Vector[RJobLog]] = def findLogs(jobId: Ident): F[Vector[RJobLog]] =
store.transact(RJobLog.findLogs(jobId)) store.transact(RJobLog.findLogs(jobId))
@ -300,13 +293,12 @@ object JoexAppImpl extends MailAddressCodec {
sch, sch,
queue, queue,
pstore, pstore,
joex.notifyAllNodes pubSubT
) )
app = new JoexAppImpl( app = new JoexAppImpl(
cfg, cfg,
store, store,
queue, queue,
pubSubT,
pstore, pstore,
termSignal, termSignal,
notificationMod, notificationMod,

View File

@ -9,13 +9,10 @@ package docspell.joex.pagecount
import cats.effect._ import cats.effect._
import cats.implicits._ import cats.implicits._
import fs2.{Chunk, Stream} import fs2.{Chunk, Stream}
import docspell.backend.JobFactory import docspell.backend.JobFactory
import docspell.backend.ops.OJoex import docspell.backend.ops.OJoex
import docspell.common._ import docspell.common._
import docspell.scheduler.Context import docspell.scheduler.{Context, JobQueue, Task}
import docspell.scheduler.Task
import docspell.store.queue.JobQueue
import docspell.store.records.RAttachment import docspell.store.records.RAttachment
import docspell.store.records.RJob import docspell.store.records.RJob

View File

@ -9,11 +9,9 @@ package docspell.joex.pdfconv
import cats.effect._ import cats.effect._
import cats.implicits._ import cats.implicits._
import fs2.{Chunk, Stream} import fs2.{Chunk, Stream}
import docspell.backend.ops.OJoex import docspell.backend.ops.OJoex
import docspell.common._ import docspell.common._
import docspell.scheduler.{Context, Task} import docspell.scheduler.{Context, JobQueue, Task}
import docspell.store.queue.JobQueue
import docspell.store.records.RAttachment import docspell.store.records.RAttachment
import docspell.store.records._ import docspell.store.records._

View File

@ -9,14 +9,11 @@ package docspell.joex.preview
import cats.effect._ import cats.effect._
import cats.implicits._ import cats.implicits._
import fs2.{Chunk, Stream} import fs2.{Chunk, Stream}
import docspell.backend.JobFactory import docspell.backend.JobFactory
import docspell.backend.ops.OJoex import docspell.backend.ops.OJoex
import docspell.common.MakePreviewArgs.StoreMode import docspell.common.MakePreviewArgs.StoreMode
import docspell.common._ import docspell.common._
import docspell.scheduler.Context import docspell.scheduler.{Context, JobQueue, Task}
import docspell.scheduler.Task
import docspell.store.queue.JobQueue
import docspell.store.records.RAttachment import docspell.store.records.RAttachment
import docspell.store.records.RJob import docspell.store.records.RJob

View File

@ -9,10 +9,9 @@ package docspell.restserver
import cats.effect.Async import cats.effect.Async
import fs2.Stream import fs2.Stream
import fs2.concurrent.Topic import fs2.concurrent.Topic
import docspell.backend.msg.{JobDone, JobSubmitted}
import docspell.pubsub.api.PubSubT import docspell.pubsub.api.PubSubT
import docspell.restserver.ws.OutputEvent import docspell.restserver.ws.OutputEvent
import docspell.scheduler.msg.{JobDone, JobSubmitted}
/** Subscribes to those events from docspell that are forwarded to the websocket endpoints /** Subscribes to those events from docspell that are forwarded to the websocket endpoints
*/ */

View File

@ -4,11 +4,10 @@
* SPDX-License-Identifier: AGPL-3.0-or-later * SPDX-License-Identifier: AGPL-3.0-or-later
*/ */
package docspell.store.queue package docspell.scheduler
import cats.effect._ import cats.effect._
import cats.implicits._ import cats.implicits._
import docspell.common._ import docspell.common._
import docspell.store.Store import docspell.store.Store
import docspell.store.queries.QJob import docspell.store.queries.QJob
@ -40,7 +39,7 @@ trait JobQueue[F[_]] {
} }
object JobQueue { object JobQueue {
def apply[F[_]: Async](store: Store[F]): Resource[F, JobQueue[F]] = private[scheduler] def create[F[_]: Async](store: Store[F]): Resource[F, JobQueue[F]] =
Resource.pure[F, JobQueue[F]](new JobQueue[F] { Resource.pure[F, JobQueue[F]](new JobQueue[F] {
private[this] val logger = docspell.logging.getLogger[F] private[this] val logger = docspell.logging.getLogger[F]

View File

@ -27,6 +27,7 @@ trait PeriodicScheduler[F[_]] {
def periodicAwake: F[Fiber[F, Throwable, Unit]] def periodicAwake: F[Fiber[F, Throwable, Unit]]
def notifyChange: F[Unit] def notifyChange: F[Unit]
}
object PeriodicScheduler {} /** Starts listening for notify messages in the background. */
def startSubscriptions: F[Unit]
}

View File

@ -22,6 +22,10 @@ trait Scheduler[F[_]] {
def notifyChange: F[Unit] def notifyChange: F[Unit]
/** Starts reacting on notify and cancel messages. */
def startSubscriptions: F[Unit]
/** Starts the schedulers main loop. */
def start: Stream[F, Nothing] def start: Stream[F, Nothing]
/** Requests to shutdown the scheduler. /** Requests to shutdown the scheduler.

View File

@ -4,11 +4,10 @@
* SPDX-License-Identifier: AGPL-3.0-or-later * SPDX-License-Identifier: AGPL-3.0-or-later
*/ */
package docspell.backend.msg package docspell.scheduler.msg
import docspell.common._ import docspell.common._
import docspell.pubsub.api.{Topic, TypedTopic} import docspell.pubsub.api.{Topic, TypedTopic}
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder} import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import io.circe.{Decoder, Encoder} import io.circe.{Decoder, Encoder}

View File

@ -4,17 +4,15 @@
* SPDX-License-Identifier: AGPL-3.0-or-later * SPDX-License-Identifier: AGPL-3.0-or-later
*/ */
package docspell.backend.msg package docspell.scheduler.msg
import cats.effect._ import cats.effect._
import cats.implicits._ import cats.implicits._
import docspell.common.{Duration, Ident, Priority} import docspell.common.{Duration, Ident, Priority}
import docspell.notification.api.Event import docspell.notification.api.{Event, EventSink}
import docspell.notification.api.EventSink
import docspell.pubsub.api.PubSubT import docspell.pubsub.api.PubSubT
import docspell.scheduler.JobQueue
import docspell.store.Store import docspell.store.Store
import docspell.store.queue.JobQueue
import docspell.store.records.RJob import docspell.store.records.RJob
final class JobQueuePublish[F[_]: Sync]( final class JobQueuePublish[F[_]: Sync](
@ -76,5 +74,5 @@ object JobQueuePublish {
pubSub: PubSubT[F], pubSub: PubSubT[F],
eventSink: EventSink[F] eventSink: EventSink[F]
): Resource[F, JobQueue[F]] = ): Resource[F, JobQueue[F]] =
JobQueue(store).map(q => new JobQueuePublish[F](q, pubSub, eventSink)) JobQueue.create(store).map(q => new JobQueuePublish[F](q, pubSub, eventSink))
} }

View File

@ -4,10 +4,10 @@
* SPDX-License-Identifier: AGPL-3.0-or-later * SPDX-License-Identifier: AGPL-3.0-or-later
*/ */
package docspell.backend.msg package docspell.scheduler.msg
import docspell.common._ import docspell.common._
import docspell.pubsub.api.{Topic, TypedTopic} import docspell.pubsub.api.{Topic, TypedTopic}
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder} import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
import io.circe.{Decoder, Encoder} import io.circe.{Decoder, Encoder}

View File

@ -0,0 +1,9 @@
package docspell.scheduler.msg
import docspell.pubsub.api.{Topic, TypedTopic}
/** A generic notification to the job executors to look for new work. */
object JobsNotify {
def apply(): TypedTopic[Unit] =
TypedTopic[Unit](Topic("jobs-notify"))
}

View File

@ -0,0 +1,9 @@
package docspell.scheduler.msg
import docspell.pubsub.api.{Topic, TypedTopic}
/** A generic notification to the periodic task scheduler to look for new work. */
object PeriodicTaskNotify {
def apply(): TypedTopic[Unit] =
TypedTopic[Unit](Topic("periodic-task-notify"))
}

View File

@ -4,7 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later * SPDX-License-Identifier: AGPL-3.0-or-later
*/ */
package docspell.store.queue package docspell.scheduler.impl
sealed trait Marked[+A] {} sealed trait Marked[+A] {}

View File

@ -1,8 +1,8 @@
package docspell.scheduler.impl package docspell.scheduler.impl
import cats.effect._ import cats.effect._
import docspell.pubsub.api.PubSubT
import docspell.scheduler._ import docspell.scheduler._
import docspell.store.queue.{JobQueue, PeriodicTaskStore}
import fs2.concurrent.SignallingRef import fs2.concurrent.SignallingRef
object PeriodicSchedulerBuilder { object PeriodicSchedulerBuilder {
@ -12,7 +12,7 @@ object PeriodicSchedulerBuilder {
sch: Scheduler[F], sch: Scheduler[F],
queue: JobQueue[F], queue: JobQueue[F],
store: PeriodicTaskStore[F], store: PeriodicTaskStore[F],
notifyJoex: F[Unit] pubsub: PubSubT[F]
): Resource[F, PeriodicScheduler[F]] = ): Resource[F, PeriodicScheduler[F]] =
for { for {
waiter <- Resource.eval(SignallingRef(true)) waiter <- Resource.eval(SignallingRef(true))
@ -22,7 +22,7 @@ object PeriodicSchedulerBuilder {
sch, sch,
queue, queue,
store, store,
notifyJoex, pubsub,
waiter, waiter,
state state
) )

View File

@ -10,13 +10,12 @@ import cats.effect._
import cats.implicits._ import cats.implicits._
import fs2._ import fs2._
import fs2.concurrent.SignallingRef import fs2.concurrent.SignallingRef
import docspell.common._ import docspell.common._
import docspell.pubsub.api.PubSubT
import docspell.scheduler._ import docspell.scheduler._
import docspell.scheduler.impl.PeriodicSchedulerImpl.State import docspell.scheduler.impl.PeriodicSchedulerImpl.State
import docspell.store.queue._ import docspell.scheduler.msg.{JobsNotify, PeriodicTaskNotify}
import docspell.store.records.RPeriodicTask import docspell.store.records.RPeriodicTask
import eu.timepit.fs2cron.calev.CalevScheduler import eu.timepit.fs2cron.calev.CalevScheduler
final class PeriodicSchedulerImpl[F[_]: Async]( final class PeriodicSchedulerImpl[F[_]: Async](
@ -24,7 +23,7 @@ final class PeriodicSchedulerImpl[F[_]: Async](
sch: Scheduler[F], sch: Scheduler[F],
queue: JobQueue[F], queue: JobQueue[F],
store: PeriodicTaskStore[F], store: PeriodicTaskStore[F],
joexNotifyAll: F[Unit], pubSub: PubSubT[F],
waiter: SignallingRef[F, Boolean], waiter: SignallingRef[F, Boolean],
state: SignallingRef[F, State[F]] state: SignallingRef[F, State[F]]
) extends PeriodicScheduler[F] { ) extends PeriodicScheduler[F] {
@ -49,6 +48,13 @@ final class PeriodicSchedulerImpl[F[_]: Async](
def notifyChange: F[Unit] = def notifyChange: F[Unit] =
waiter.update(b => !b) waiter.update(b => !b)
def startSubscriptions: F[Unit] =
for {
_ <- Async[F].start(pubSub.subscribeSink(PeriodicTaskNotify()) { _ =>
logger.info("Notify periodic scheduler from message") *> notifyChange
})
} yield ()
// internal // internal
/** On startup, get all periodic jobs from this scheduler and remove the mark, so they /** On startup, get all periodic jobs from this scheduler and remove the mark, so they
@ -117,7 +123,7 @@ final class PeriodicSchedulerImpl[F[_]: Async](
} }
def notifyJoex: F[Unit] = def notifyJoex: F[Unit] =
sch.notifyChange *> joexNotifyAll sch.notifyChange *> pubSub.publish1IgnoreErrors(JobsNotify(), ()).void
def scheduleNotify(pj: RPeriodicTask): F[Unit] = def scheduleNotify(pj: RPeriodicTask): F[Unit] =
Timestamp Timestamp

View File

@ -4,11 +4,10 @@
* SPDX-License-Identifier: AGPL-3.0-or-later * SPDX-License-Identifier: AGPL-3.0-or-later
*/ */
package docspell.store.queue package docspell.scheduler.impl
import cats.effect._ import cats.effect._
import cats.implicits._ import cats.implicits._
import docspell.common._ import docspell.common._
import docspell.store.queries.QPeriodicTask import docspell.store.queries.QPeriodicTask
import docspell.store.records._ import docspell.store.records._
@ -57,7 +56,7 @@ object PeriodicTaskStore {
case false => Marked.notMarkable case false => Marked.notMarkable
} }
case None => case None =>
Marked.notFound.pure[F] Marked.notFound[RPeriodicTask].pure[F]
} }
Resource.make(chooseNext) { Resource.make(chooseNext) {

View File

@ -10,12 +10,10 @@ import cats.effect._
import cats.effect.std.Semaphore import cats.effect.std.Semaphore
import cats.implicits._ import cats.implicits._
import fs2.concurrent.SignallingRef import fs2.concurrent.SignallingRef
import docspell.scheduler.{JobQueue, _}
import docspell.scheduler._
import docspell.notification.api.EventSink import docspell.notification.api.EventSink
import docspell.pubsub.api.PubSubT import docspell.pubsub.api.PubSubT
import docspell.store.Store import docspell.store.Store
import docspell.store.queue.JobQueue
case class SchedulerBuilder[F[_]: Async]( case class SchedulerBuilder[F[_]: Async](
config: SchedulerConfig, config: SchedulerConfig,
@ -88,7 +86,7 @@ object SchedulerBuilder {
config, config,
JobTaskRegistry.empty[F], JobTaskRegistry.empty[F],
store, store,
JobQueue(store), JobQueue.create(store),
LogSink.db[F](store), LogSink.db[F](store),
PubSubT.noop[F], PubSubT.noop[F],
EventSink.silent[F] EventSink.silent[F]

View File

@ -12,19 +12,16 @@ import cats.effect.std.Semaphore
import cats.implicits._ import cats.implicits._
import fs2.Stream import fs2.Stream
import fs2.concurrent.SignallingRef import fs2.concurrent.SignallingRef
import docspell.scheduler.msg.{CancelJob, JobDone, JobsNotify}
import docspell.scheduler.msg.JobDone
import docspell.common._ import docspell.common._
import docspell.scheduler._ import docspell.scheduler.{JobQueue, _}
import docspell.scheduler.impl.SchedulerImpl._ import docspell.scheduler.impl.SchedulerImpl._
import docspell.notification.api.Event import docspell.notification.api.Event
import docspell.notification.api.EventSink import docspell.notification.api.EventSink
import docspell.pubsub.api.PubSubT import docspell.pubsub.api.PubSubT
import docspell.store.Store import docspell.store.Store
import docspell.store.queries.QJob import docspell.store.queries.QJob
import docspell.store.queue.JobQueue
import docspell.store.records.RJob import docspell.store.records.RJob
import io.circe.Json import io.circe.Json
final class SchedulerImpl[F[_]: Async]( final class SchedulerImpl[F[_]: Async](
@ -42,6 +39,16 @@ final class SchedulerImpl[F[_]: Async](
private[this] val logger = docspell.logging.getLogger[F] private[this] val logger = docspell.logging.getLogger[F]
def startSubscriptions =
for {
_ <- Async[F].start(pubSub.subscribeSink(JobsNotify()) { _ =>
notifyChange
})
_ <- Async[F].start(pubSub.subscribeSink(CancelJob.topic) { msg =>
requestCancel(msg.body.jobId).void
})
} yield ()
/** On startup, get all jobs in state running from this scheduler and put them into /** On startup, get all jobs in state running from this scheduler and put them into
* waiting state, so they get picked up again. * waiting state, so they get picked up again.
*/ */

View File

@ -89,7 +89,7 @@ trait UserTaskStore[F[_]] {
implicit E: Encoder[A] implicit E: Encoder[A]
): F[UserTask[String]] ): F[UserTask[String]]
/** Delete all tasks of the given user that have name `name'. */ /** Delete all tasks of the given user that have name `name`. */
def deleteAll(scope: UserTaskScope, name: Ident): F[Int] def deleteAll(scope: UserTaskScope, name: Ident): F[Int]
} }