Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 8 additions & 36 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,41 +1,13 @@
name: CI

on: [push, pull_request]
on:
push:
branches: [ master ]
pull_request:

jobs:
test:

runs-on: ubuntu-latest

strategy:
matrix:
scala:
- 2.13.5
- 2.12.13

steps:
- uses: actions/checkout@v2

- uses: coursier/cache-action@v5

- name: scala
uses: olafurpg/setup-scala@v10
with:
java-version: openjdk@1.11

- name: build ${{ matrix.scala }}
run: sbt ++${{ matrix.scala }} clean coverage test

- name: test coverage
if: success()
env:
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
run: sbt ++${{ matrix.scala }} coverageReport coverageAggregate coveralls

- name: slack
uses: homoluctus/slatify@master
if: failure() && github.ref == 'refs/heads/master'
with:
type: ${{ job.status }}
job_name: Build
url: ${{ secrets.SLACK_WEBHOOK }}
uses: evolution-gaming/scala-github-actions/.github/workflows/ci.yml@dde27b9bd793d41d5aacf8fb74403c9de5da1146 # v6.3.0
# TODO remove after first release with Scala 3 support

Check warning on line 11 in .github/workflows/ci.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Complete the task associated to this "TODO" comment.

See more on https://sonarcloud.io/project/issues?id=evolution-gaming_serially&issues=AZ_1oBAjMsvR0MTPBA0l&open=AZ_1oBAjMsvR0MTPBA0l&pullRequest=155
with:
version_policy_check: false
Comment on lines 9 to +13

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- workflow files ---'
git ls-files '.github/workflows/*' '.github/actions/*' | sort
printf '%s\n' '--- caller workflow ---'
cat -n .github/workflows/ci.yml
printf '%s\n' '--- permission and reusable-workflow references ---'
rg -n --hidden --glob '!.git/**' \
  'permissions:|contents:|packages:|id-token:|pull-requests:|actions:|version_policy_check|evolution-gaming/scala-github-actions' \
  .github README.md 2>/dev/null || true
printf '%s\n' '--- repository status ---'
git status --short

Repository: evolution-gaming/serially

Length of output: 1064


🌐 Web query:

evolution-gaming/scala-github-actions v6.3.0 reusable workflow permissions ci.yml

💡 Result:

The evolution-gaming/scala-github-actions repository provides reusable workflows (such as release.yml) to simplify CI/CD for Scala projects [1][2][3]. When integrating these reusable workflows into a local ci.yml or release.yml file, the permissions requirement is governed by standard GitHub Actions security principles [4][5][6]. Key points regarding permissions for these reusable workflows: 1. Inheritance and Security: Reusable workflows run with the permissions explicitly granted at the job level in the calling workflow [4][6]. If you do not define permissions in the calling job, it will inherit the default permissions of the GITHUB_TOKEN [4]. 2. Principle of Least Privilege: Because reusable workflows act like an extension of your own pipeline, you should assign only the minimum permissions necessary [6]. For a release workflow that involves publishing artifacts (often to Artifactory) or creating GitHub releases, the workflow typically requires permissions such as contents: write (for releases) and potentially others depending on the specific tasks performed by the reusable action [4][6]. 3. Permission Downgrading: If you define permissions in both the calling workflow and the reusable workflow, GitHub enforces a strict rule: the called workflow's permissions can only be equal to or more restrictive than the permissions granted by the caller [4][5]. They cannot be elevated [5]. 4. Configuration Example: In your calling workflow (e.g., ci.yml or release.yml), you define permissions at the job level [4][6]: jobs: release: permissions: contents: write # Required if the workflow creates GitHub Releases uses: evolution-gaming/scala-github-actions/.github/workflows/release.yml@v4 secrets: inherit Always check the specific requirements of the version you are using (e.g., v4) [1], as updates to the underlying reusable workflow may change the operations it performs and the associated permission needs [6]. If the reusable workflow fails with a permission error, review the logs to identify which specific scope (e.g., packages, contents) is being denied [6].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
repo='evolution-gaming/scala-github-actions'
sha='dde27b9bd793d41d5aacf8fb74403c9de5da1146'
tag='v6.3.0'

printf '%s\n' '--- pinned workflow ---'
curl -fsSL "https://raw.githubusercontent.com/$repo/$sha/.github/workflows/ci.yml" | nl -ba

printf '%s\n' '--- permission-related declarations in pinned workflow ---'
curl -fsSL "https://raw.githubusercontent.com/$repo/$sha/.github/workflows/ci.yml" |
  rg -n -C 3 'permissions:|contents:|packages:|id-token:|pull-requests:|actions:|github-token|GITHUB_TOKEN' || true

printf '%s\n' '--- tag ref ---'
ref_json="$(curl -fsSL "https://api.github.com/repos/$repo/git/ref/tags/$tag")"
printf '%s\n' "$ref_json" | jq '{ref, object}'

type="$(printf '%s\n' "$ref_json" | jq -r '.object.type')"
tag_object="$(printf '%s\n' "$ref_json" | jq -r '.object.sha')"
if [ "$type" = tag ]; then
  commit="$(curl -fsSL "https://api.github.com/repos/$repo/git/tags/$tag_object" | jq -r '.object.sha')
else
  commit="$tag_object"
fi
printf 'dereferenced_tag_commit=%s\n' "$commit"

printf '%s\n' '--- commit existence checks ---'
curl -fsSL "https://api.github.com/repos/$repo/commits/$sha" | jq -r '.sha'
curl -fsSL "https://api.github.com/repos/$repo/commits/$commit" | jq -r '.sha'

Repository: evolution-gaming/serially

Length of output: 300


🏁 Script executed:

#!/bin/bash
set -eu
repo='evolution-gaming/scala-github-actions'
sha='dde27b9bd793d41d5aacf8fb74403c9de5da1146'
tag='v6.3.0'

workflow="$(curl -fsSL "https://raw.githubusercontent.com/$repo/$sha/.github/workflows/ci.yml")"
printf '%s\n' '--- pinned workflow ---'
awk '{printf "%5d\t%s\n", NR, $0}' <<<"$workflow"

printf '%s\n' '--- permission-related declarations in pinned workflow ---'
grep -n -C 3 -E 'permissions:|contents:|packages:|id-token:|pull-requests:|actions:|github-token|GITHUB_TOKEN' <<<"$workflow" || true

printf '%s\n' '--- tag ref ---'
ref_json="$(curl -fsSL "https://api.github.com/repos/$repo/git/ref/tags/$tag")"
printf '%s\n' "$ref_json" | jq '{ref, object}'

type="$(printf '%s\n' "$ref_json" | jq -r '.object.type')"
tag_object="$(printf '%s\n' "$ref_json" | jq -r '.object.sha')"
if [ "$type" = tag ]; then
  commit="$(curl -fsSL "https://api.github.com/repos/$repo/git/tags/$tag_object" | jq -r '.object.sha')"
else
  commit="$tag_object"
fi
printf 'dereferenced_tag_commit=%s\n' "$commit"

printf '%s\n' '--- commit existence checks ---'
curl -fsSL "https://api.github.com/repos/$repo/commits/$sha" | jq -r '.sha'
curl -fsSL "https://api.github.com/repos/$repo/commits/$commit" | jq -r '.sha'

Repository: evolution-gaming/serially

Length of output: 9431


Declare least-privilege permissions for the reusable CI job.

The called workflow checks out repository contents. Add contents: read and grant no broader token permissions.

🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis

[warning] 11-11: Complete the task associated to this "TODO" comment.

See more on https://sonarcloud.io/project/issues?id=evolution-gaming_serially&issues=AZ_1oBAjMsvR0MTPBA0l&open=AZ_1oBAjMsvR0MTPBA0l&pullRequest=155

🪛 zizmor (1.29.0)

[warning] 9-13: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block

(excessive-permissions)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 9 - 13, Update the reusable CI job
declaration under test to grant the least-privilege GitHub token permission
contents: read, with no broader permissions enabled. Keep the existing uses and
with configuration unchanged.

Source: Linters/SAST tools

Comment on lines +11 to +13

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Track and re-enable the version-policy check.

version_policy_check: false disables the version-policy gate introduced by this PR. The TODO has no linked issue or automated release condition.

After Scala 3 support is available, set this value to true. Until then, link a tracking issue and make re-enablement a release prerequisite.

🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis

[warning] 11-11: Complete the task associated to this "TODO" comment.

See more on https://sonarcloud.io/project/issues?id=evolution-gaming_serially&issues=AZ_1oBAjMsvR0MTPBA0l&open=AZ_1oBAjMsvR0MTPBA0l&pullRequest=155

🪛 zizmor (1.29.0)

[warning] 9-13: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block

(excessive-permissions)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 11 - 13, Update the CI workflow’s
version_policy_check configuration to track Scala 3 support explicitly: add a
linked tracking issue to the TODO and document that re-enabling the check is a
prerequisite for the first Scala 3-supported release, then set
version_policy_check to true once that release condition is met.

Source: Linters/SAST tools

10 changes: 5 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
name: Publish new Release
name: Publish Release

on:
release:
types: [published]
branches: [master]
push:
tags:
- 'v*'

jobs:
release:
uses: evolution-gaming/scala-github-actions/.github/workflows/release.yml@v1
uses: evolution-gaming/scala-github-actions/.github/workflows/release.yml@v5
secrets: inherit
Comment on lines +10 to 11

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="evolution-gaming/scala-github-actions"
sha="$(gh api "repos/${repo}/commits/v5" --jq '.sha')"
echo "Resolved v5 SHA: ${sha}"

gh api "repos/${repo}/contents/.github/workflows/release.yml?ref=${sha}" \
  --jq '.content' | tr -d '\n' | base64 --decode |
  grep -nE 'workflow_call|secrets:|secrets\.'

Repository: evolution-gaming/serially

Length of output: 386


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="evolution-gaming/scala-github-actions"
sha="b4557d9a82c03596dc2425e19b3cb9a6280a4739"

echo "Pinned commit:"
gh api "repos/${repo}/commits/${sha}" --jq '.sha'

echo
echo "Reusable workflow:"
gh api "repos/${repo}/contents/.github/workflows/release.yml?ref=${sha}" \
  --jq '.content' | tr -d '\n' | base64 --decode | nl -ba | sed -n '1,90p'

echo
echo "Declared tags and current tag target:"
gh api "repos/${repo}/git/ref/tags/v5" --jq '{type: .object.type, sha: .object.sha}'
gh api "repos/${repo}/commits/v5" --jq '.sha'

Repository: evolution-gaming/serially

Length of output: 280


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="evolution-gaming/scala-github-actions"
sha="b4557d9a82c03596dc2425e19b3cb9a6280a4739"

echo "Reusable workflow:"
gh api "repos/${repo}/contents/.github/workflows/release.yml?ref=${sha}" \
  --jq '.content' | tr -d '\n' | base64 --decode | sed -n '1,90p'

echo
echo "Tag ref:"
gh api "repos/${repo}/git/ref/tags/v5" --jq '{type: .object.type, sha: .object.sha}'

echo
echo "Tag commit resolution:"
gh api "repos/${repo}/commits/v5" --jq '.sha'

Repository: evolution-gaming/serially

Length of output: 2352


🌐 Web query:

GitHub Actions reusable workflow workflow_call secrets inherit explicit secret not declared called workflow secrets context GITHUB_TOKEN

💡 Result:

In GitHub Actions, reusable workflows require explicit configuration to access secrets [1][2]. They do not automatically inherit secrets from the calling workflow, including the GITHUB_TOKEN [1][2][3]. When using secrets: inherit in the calling workflow, the reusable workflow gains access to all secrets available to the caller (including organization, repository, and environment secrets) without requiring each to be individually declared in the caller [4][5][6]. Regarding the specific points raised: 1. Secrets Not Declared: If you do not use secrets: inherit, any secret passed explicitly must be declared in the reusable workflow's on.workflow_call.secrets block [7][2]. If a workflow attempts to reference a secret that has not been declared or inherited, it will be unavailable (resolving to an empty string) [1][2]. 2. Explicit Secret Not Declared: If a caller attempts to pass a secret that the reusable workflow has not declared in its on.workflow_call.secrets, it may cause a parsing or runtime error depending on the configuration [7]. A reusable workflow must explicitly define any secrets it expects to receive unless the inherit keyword is used to pass the entire set [7][1][2]. 3. GITHUB_TOKEN Behavior: The GITHUB_TOKEN is a special, automatically generated token provided by GitHub Actions for each job [3]. Unlike repository or environment secrets, it is not passed via the secrets: inherit or explicit secrets: mapping [3]. It is automatically available to every job in every workflow (including reusable workflows) by default, provided the job has sufficient permissions [3]. You do not need to (and should not) pass it as a secret to a reusable workflow; it is already present in the execution context of the called workflow's jobs [3]. In summary, secrets: inherit provides a blanket way to pass all available secrets to a reusable workflow, while named secrets must be declared in the on.workflow_call block of the called workflow to be accessible [7][1]. The GITHUB_TOKEN is handled independently by the runner and is available without manual secret passing [3].

Citations:


Pin the release workflow and pass only JFROG_ACCESS_TOKEN.

Replace @v5 with the verified commit @b4557d9a82c03596dc2425e19b3cb9a6280a4739. Declare JFROG_ACCESS_TOKEN in the called workflow's workflow_call.secrets, then map only that secret. GITHUB_TOKEN is provided automatically.

🧰 Tools
🪛 zizmor (1.29.0)

[warning] 10-10: secrets unconditionally inherited by called workflow (secrets-inherit): this reusable workflow

(secrets-inherit)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 10 - 11, Update the release
workflow reference to the verified commit
b4557d9a82c03596dc2425e19b3cb9a6280a473 instead of the mutable v5 tag. In the
release workflow’s workflow_call.secrets declaration, expose JFROG_ACCESS_TOKEN
and map only that secret to the called workflow; remove any explicit
GITHUB_TOKEN mapping because it is provided automatically.

Source: Linters/SAST tools

96 changes: 96 additions & 0 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Main goals:
# - nicer commit diffs (trailing commas, no alignment for pattern matching, force new lines)
# - better interop with default IntelliJ IDEA setup (matching import and modifiers sorting logic)
# - better developer experience on laptop screens (like 16' MBPs) with IntelliJ IDEA (line wraps)

version = 3.11.5

runner.dialect = scala213source3

# only format files tracked by git
project.git = true

maxColumn = 120
trailingCommas = always

preset = default
# do not align to make nicer commit diffs
align.preset = none

indent {
# altering defnSite and extendSite to have this:
# final class MyErr extends RuntimeException(
# "super error message",
# )
# instead of this:
# final class MyErr extends RuntimeException(
# "super error message",
# )
defnSite = 2
extendSite = 0
}

spaces {
# makes string interpolation with curlies more visually distinct
inInterpolatedStringCurlyBraces = true
}

newlines {
# keep author new lines where possible
source = keep
# force new line after "(implicit" for multi-line arg lists
implicitParamListModifierForce = [after]
avoidForSimpleOverflow = [
tooLong, # if the line would be too long even after newline inserted, do nothing
slc, # do nothing if overflow caused by single line comment
]
}

verticalMultiline {
atDefnSite = true
arityThreshold = 4 # more than 3 args in a list will be turned vertical
newlineAfterOpenParen = true # for nicer commit diffs
}

# for nicer commit diffs - forces new line before last parenthesis:
# class MyCls(
# arg1: String,
# arg2: String,
# ) extends MyTrait {
#
# without it:
# class MyCls(
# arg1: String,
# arg2: String) extends MyTrait {
danglingParentheses.exclude = []

docstrings {
# easier to view diffs in IDEA on 16' MBP screen if docs max line are shorter than code
wrapMaxColumn = 100
# next settings make it similar to the default IDEA javadoc formatting
style = Asterisk
oneline = unfold
blankFirstLine = unfold
}

rewrite.rules = [
Imports,
RedundantParens,
SortModifiers,
prefercurlyfors,
]

# put visibility modifier first
rewrite.sortModifiers.preset = styleGuide

# Import sorting as similar as possible to scalafix's "OrganizeImports.preset = INTELLIJ_2020_3".
# Scalafix is not used as its commands mess up "all .." build aliases and it takes long time to run,
# while its code semantic based features are not needed here.
# I.e. detection of unused imports is done with Scala compiler options.
rewrite.imports {
sort = ascii
groups = [
[".*"],
["java\\..*", "javax\\..*", "scala\\..*"],
]
}
63 changes: 51 additions & 12 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,33 +1,72 @@
import sbtversionpolicy.Compatibility.BinaryCompatible

name := "serially"

organization := "com.evolutiongaming"

homepage := Some(url("https://github.com/evolution-gaming/serially"))
homepage := Some(uri("https://github.com/evolution-gaming/serially"))

startYear := Some(2018)

organizationName := "Evolution"

organizationHomepage := Some(url("https://evolution.com"))
organizationHomepage := Some(uri("https://evolution.com"))

scalaVersion := crossScalaVersions.value.head

crossScalaVersions := Seq("2.13.5", "2.12.13")
crossScalaVersions := Seq("2.13.18", "3.3.8")

scalacOptions ++= crossSettings(
scalaVersion = scalaVersion.value,
// Good compiler options for Scala 2.13 are coming from com.evolution:sbt-scalac-opts-plugin:0.1.0,
// but its support for Scala 3 is limited, especially what concerns linting options.
//
// If Scala 3 is made the primary target, good linting scalac options for it should be added first.
if3 = Seq(
"-Ykind-projector:underscores",

// disable new brace-less syntax:
// https://alexn.org/blog/2022/10/24/scala-3-optional-braces/
"-no-indent",

// improve error messages:
"-explain",
"-explain-types",
),
if2 = Seq(
"-Xsource:3",
),
)

Compile / doc / scalacOptions ++= Seq("-groups", "-implicits", "-no-link-warnings")

publishTo := Some(Resolver.evolutionReleases)

versionPolicyIntention := BinaryCompatible

libraryDependencies ++= Seq(
"com.evolutiongaming" %% "future-helper" % "1.0.6",
"com.typesafe.akka" %% "akka-actor" % "2.6.8",
"com.typesafe.akka" %% "akka-testkit" % "2.6.8" % Test,
"org.scalatest" %% "scalatest" % "3.2.9" % Test)
"com.evolutiongaming" %% "future-helper" % "1.0.7",
"com.typesafe.akka" %% "akka-actor" % "2.6.21", // scala-steward:off
"com.typesafe.akka" %% "akka-testkit" % "2.6.21" % Test, // scala-steward:off
"org.scalatest" %% "scalatest" % "3.2.20" % Test,
)

// TODO remove after first release with Scala 3 support
versionPolicyIgnored ++= Seq(
// add libraries here that are known to be binary compatible, like:
"com.typesafe.akka" %% "akka-actor",
"org.scala-lang.modules" %% "scala-java8-compat",
)

licenses := Seq(("MIT", url("https://opensource.org/licenses/MIT")))
licenses := Seq(("MIT", uri("https://opensource.org/licenses/MIT")))

releaseCrossBuild := true
def crossSettings[T](scalaVersion: String, if3: T, if2: T): T = {
scalaVersion match {
case version if version.startsWith("3") => if3
case _ => if2
}
}

//addCommandAlias("check", "all versionPolicyCheck Compile/doc")
addCommandAlias("check", "show version")
addCommandAlias("build", "+all compile test")
addCommandAlias("check", "+all scalafmtCheckRepo versionPolicyCheck Compile/doc")
addCommandAlias("fmt", "+all scalafmtRepo")
addCommandAlias("build", "+all compile testFull")
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=1.5.2
sbt.version = 2.0.6
12 changes: 7 additions & 5 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.7.2")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.4.4")

addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.2.7")

addSbtPlugin("com.github.sbt" % "sbt-release" % "1.0.15")
addSbtPlugin("com.github.sbt" % "sbt-dynver" % "5.1.1")

addSbtPlugin("com.evolution" % "sbt-scalac-opts-plugin" % "0.2.0")

addSbtPlugin("com.evolution" % "sbt-artifactory-plugin" % "0.1.2")
addSbtPlugin("com.evolution" % "sbt-artifactory-plugin" % "0.1.2")

addSbtPlugin("ch.epfl.scala" % "sbt-version-policy" % "3.3.0")

addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.6.2")
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.evolutiongaming.concurrent.serially

import akka.actor.ActorRefFactory
import com.evolutiongaming.concurrent.CurrentThreadExecutionContext
import com.evolutiongaming.concurrent.FutureHelper._
import com.evolutiongaming.concurrent.FutureHelper.*

import scala.concurrent.Future

Expand All @@ -15,20 +15,24 @@ trait AsyncVar[S] extends StateVar[S] {

object AsyncVar {

def apply[S](state: S)(implicit factory: ActorRefFactory): AsyncVar[S] = {
def apply[S](
state: S,
)(implicit
factory: ActorRefFactory,
): AsyncVar[S] = {
val serially = SeriallyAsync()
apply(state, serially)
}

def apply[S](state: S, serially: SeriallyAsync): AsyncVar[S] = {

implicit val ec = CurrentThreadExecutionContext
implicit val ec: CurrentThreadExecutionContext.type = CurrentThreadExecutionContext

@volatile var s = state

new AsyncVar[S] {

def value() = s
def value(): S = s

def async[SS](f: S => Future[(S, SS)]): Future[SS] = {
serially.async[SS] {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package com.evolutiongaming.concurrent.serially

import java.util.concurrent.atomic.AtomicBoolean

import akka.actor.{Actor, ActorRef, ActorRefFactory, Props}

import java.util.concurrent.atomic.AtomicBoolean
import scala.concurrent.{Future, Promise}
import scala.util.Try
import scala.util.control.NoStackTrace
Expand All @@ -19,24 +18,24 @@ object Serially {

private val StoppedFailure = Future.failed(Stopped)

def apply(name: Option[String] = None)(implicit factory: ActorRefFactory): Serially = {

case class Func(f: () => Unit)
case class StopPrepare(promise: Promise[Unit])
case class StopCommit(promise: Promise[Unit])
def apply(
name: Option[String] = None,
)(implicit
factory: ActorRefFactory,
): Serially = {

def actor() = new Actor {
def receive: Receive = {
case Func(f) => f()
case Func(f) => f()
case StopPrepare(promise) => self.tell(StopCommit(promise), ActorRef.noSender)
case StopCommit(promise) => promise.success(()); context.stop(self);
case StopCommit(promise) => promise.success(()); context.stop(self);
}
}

val props = Props(actor())

val ref = name match {
case None => factory.actorOf(props)
case None => factory.actorOf(props)
case Some(name) => factory.actorOf(props, name)
}

Expand All @@ -61,7 +60,7 @@ object Serially {
}
}

def stop() = {
def stop(): Future[Unit] = {
running {
if (stopped.compareAndSet(false, true)) {
val promise = Promise[Unit]()
Expand All @@ -75,14 +74,16 @@ object Serially {
}
}


def now: Serially = new Serially {

def apply[T](f: => T): Future[T] = Future.fromTry(Try(f))

def stop() = Future.unit
def stop(): Future[Unit] = Future.unit
}

private case class Func(f: () => Unit)
private case class StopPrepare(promise: Promise[Unit])
private case class StopCommit(promise: Promise[Unit])

case object Stopped extends RuntimeException with NoStackTrace
}
}
Loading