Skip to content

Commit a06481e

Browse files
committed
Revert "Remove the legacy examples subproject"
This reverts commit 861b381.
1 parent d717d1b commit a06481e

22 files changed

Lines changed: 991 additions & 1 deletion

build.sbt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ compileDocumentation := {
2929
lazy val rootProject = (project in file("."))
3030
.settings(commonSettings)
3131
.settings(publishArtifact := false, name := "ox")
32-
.aggregate(core, kafka, mdcLogback, flowReactiveStreams, cron, otelContext)
32+
.aggregate(core, examples, kafka, mdcLogback, flowReactiveStreams, cron, otelContext)
3333

3434
lazy val core: Project = (project in file("core"))
3535
.settings(commonSettings)
@@ -44,6 +44,18 @@ lazy val core: Project = (project in file("core"))
4444
Test / fork := true
4545
)
4646

47+
lazy val examples: Project = (project in file("examples"))
48+
.settings(commonSettings)
49+
.settings(
50+
name := "examples",
51+
libraryDependencies ++= Seq(
52+
logback % Test,
53+
scalaTest
54+
),
55+
publishArtifact := false
56+
)
57+
.dependsOn(core)
58+
4759
lazy val kafka: Project = (project in file("kafka"))
4860
.settings(commonSettings)
4961
.settings(
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<configuration>
4+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
5+
<encoder>
6+
<pattern>%d{HH:mm:ss.SSS}%boldYellow(%replace( [%X{cid}] ){' \[\] ', ' '})[%thread] %-5level %logger{5} - %msg%n%rEx</pattern>
7+
</encoder>
8+
</appender>
9+
10+
<logger name="io.netty" level="INFO"/>
11+
<logger name="org.asynchttpclient" level="INFO"/>
12+
13+
<root level="DEBUG">
14+
<appender-ref ref="STDOUT"/>
15+
</root>
16+
</configuration>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package ox.characterization
2+
3+
import ox.sleep
4+
5+
import scala.concurrent.duration.*
6+
7+
import org.slf4j.LoggerFactory
8+
9+
@main def catchInterrupt(): Unit =
10+
val log = LoggerFactory.getLogger("catchInterrupt")
11+
val t = new Thread(() =>
12+
try
13+
sleep(1.second)
14+
log.info("T2: Done sleeping (1)")
15+
catch
16+
case _: InterruptedException =>
17+
log.info("T2: Interrupted")
18+
19+
sleep(1.second)
20+
log.info("T2: Done sleeping (2)")
21+
)
22+
t.start()
23+
24+
log.info("T1: Started")
25+
t.interrupt()
26+
log.info("T1: Interrupted")
27+
t.join()
28+
log.info("T1: Done")
29+
end catchInterrupt
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package ox.characterization
2+
3+
import ox.discard
4+
5+
@main def lazyExtension(): Unit =
6+
extension [T](t: => T)
7+
def test: T =
8+
println("In extension")
9+
t
10+
11+
{
12+
println("In block")
13+
1
14+
}.test.discard
15+
end lazyExtension
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package ox.characterization
2+
3+
import java.util.concurrent.{Callable, StructuredTaskScope}
4+
import java.util.concurrent.atomic.AtomicReference
5+
6+
@main def mutableScopedValues(): Unit =
7+
val trail = ScopedValue.newInstance[AtomicReference[List[String]]]()
8+
val result = ScopedValue.callWhere(
9+
trail,
10+
new AtomicReference(Nil),
11+
{ () =>
12+
trail.get.updateAndGet("A" :: _)
13+
14+
// val c = Thread.startVirtualThread { () =>
15+
// println("2: " + trail.get.updateAndGet("B" :: _))
16+
// }
17+
// c.join()
18+
19+
val scope1 = new StructuredTaskScope.ShutdownOnFailure()
20+
try
21+
scope1.fork(() => trail.get.updateAndGet("B" :: _))
22+
scope1.join()
23+
finally scope1.close()
24+
25+
trail.get.get()
26+
}: Callable[List[String]]
27+
)
28+
29+
println(s"Result: $result")
30+
end mutableScopedValues
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package ox.characterization
2+
3+
@main def nestedContexts(): Unit =
4+
case class Ctx(n: Int)
5+
var counter = 0
6+
7+
def ctx(f: Ctx ?=> Unit): Unit =
8+
counter += 1
9+
f(using Ctx(counter))
10+
11+
ctx {
12+
println(summon[Ctx])
13+
ctx {
14+
println(summon[Ctx])
15+
ctx {
16+
println(summon[Ctx])
17+
}
18+
}
19+
}
20+
end nestedContexts
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package ox.characterization
2+
3+
import ox.sleep
4+
5+
import org.slf4j.LoggerFactory
6+
7+
import scala.concurrent.duration.*
8+
import java.util.concurrent.StructuredTaskScope
9+
10+
@main def nestedScopes(): Unit =
11+
val log = LoggerFactory.getLogger("nestedScopes")
12+
val scope1 = new StructuredTaskScope.ShutdownOnFailure()
13+
log.info("Started scope1")
14+
try
15+
scope1.fork { () =>
16+
sleep(1.second)
17+
log.info("Child 1 done")
18+
}
19+
Thread.startVirtualThread { () =>
20+
sleep(3.seconds)
21+
log.info("Child 2 done")
22+
}
23+
val scope2 = new StructuredTaskScope.ShutdownOnFailure()
24+
log.info("Started scope2")
25+
try
26+
scope2.fork { () =>
27+
sleep(2.seconds)
28+
log.info("Child 3 done")
29+
}
30+
scope1.join()
31+
log.info("Scope 1 joined")
32+
scope2.join()
33+
log.info("Scope 2 joined")
34+
finally
35+
scope2.close()
36+
log.info("Scope 2 closed")
37+
end try
38+
finally
39+
scope1.close()
40+
log.info("Scope 1 closed")
41+
end try
42+
43+
sleep(5.seconds)
44+
end nestedScopes
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package ox.characterization
2+
3+
import ox.discard
4+
5+
import java.util.concurrent.StructuredTaskScope
6+
7+
@main def scopedValuesNesting1(): Unit =
8+
val v = ScopedValue.newInstance[String]()
9+
ScopedValue
10+
.callWhere(
11+
v,
12+
"x",
13+
() =>
14+
val scope1 = new StructuredTaskScope.ShutdownOnFailure()
15+
try
16+
scope1.fork(() => println(v.get()))
17+
scope1.join()
18+
finally scope1.close()
19+
)
20+
.discard
21+
end scopedValuesNesting1
22+
23+
@main def scopedValuesNesting2(): Unit =
24+
val v = ScopedValue.newInstance[String]()
25+
val scope1 = new StructuredTaskScope.ShutdownOnFailure()
26+
ScopedValue
27+
.callWhere(
28+
v,
29+
"x",
30+
() =>
31+
try
32+
scope1.fork(() => println(v.get()))
33+
scope1.join()
34+
finally scope1.close()
35+
)
36+
.discard
37+
end scopedValuesNesting2
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package ox.crawler
2+
3+
import org.slf4j.LoggerFactory
4+
import ox.{discard, Fork, fork, supervised}
5+
6+
import java.util.concurrent.{ArrayBlockingQueue, BlockingQueue}
7+
import scala.annotation.tailrec
8+
9+
object Crawler:
10+
private val logger = LoggerFactory.getLogger(this.getClass)
11+
12+
def crawl(crawlUrl: Url, http: Http, parseLinks: String => List[Url]): Map[Host, Int] = supervised {
13+
@tailrec
14+
def crawler(crawlerQueue: BlockingQueue[CrawlerMessage], data: CrawlerData): Map[Host, Int] =
15+
def handleMessage(msg: CrawlerMessage, data: CrawlerData): CrawlerData = msg match
16+
case Start(url) => crawlUrl(data, url)
17+
18+
case CrawlResult(url, links) =>
19+
val data2 = data.copy(inProgress = data.inProgress - url)
20+
21+
links.foldLeft(data2) { case (d, link) =>
22+
val d2 = d.copy(referenceCount = d.referenceCount.updated(link.host, d.referenceCount.getOrElse(link.host, 0) + 1))
23+
crawlUrl(d2, link)
24+
}
25+
26+
def crawlUrl(data: CrawlerData, url: Url): CrawlerData =
27+
if !data.visitedLinks.contains(url) then
28+
val (data2, workerQueue) = workerFor(data, url.host)
29+
workerQueue.put(url)
30+
data2.copy(
31+
visitedLinks = data.visitedLinks + url,
32+
inProgress = data.inProgress + url
33+
)
34+
else data
35+
36+
def workerFor(data: CrawlerData, host: Host): (CrawlerData, BlockingQueue[Url]) =
37+
data.workers.get(host) match
38+
case None =>
39+
val workerQueue = new ArrayBlockingQueue[Url](32)
40+
worker(workerQueue, crawlerQueue).discard
41+
(data.copy(workers = data.workers + (host -> workerQueue)), workerQueue)
42+
case Some(queue) => (data, queue)
43+
44+
val msg = crawlerQueue.take
45+
val data2 = handleMessage(msg, data)
46+
if data2.inProgress.isEmpty then data2.referenceCount else crawler(crawlerQueue, data2)
47+
end crawler
48+
49+
def worker(workerQueue: BlockingQueue[Url], crawlerQueue: BlockingQueue[CrawlerMessage]): Fork[Unit] =
50+
def handleUrl(url: Url): Unit =
51+
val r =
52+
try parseLinks(http.get(url))
53+
catch
54+
case e: Exception =>
55+
logger.error(s"Cannot get contents of $url", e)
56+
List.empty[Url]
57+
fork(crawlerQueue.put(CrawlResult(url, r))).discard
58+
end handleUrl
59+
60+
fork {
61+
while true do
62+
val url = workerQueue.take
63+
handleUrl(url)
64+
}
65+
end worker
66+
67+
val crawlerQueue = new ArrayBlockingQueue[CrawlerMessage](32)
68+
crawlerQueue.put(Start(crawlUrl))
69+
crawler(crawlerQueue, CrawlerData(Map(), Set(), Set(), Map()))
70+
}
71+
72+
case class CrawlerData(
73+
referenceCount: Map[Host, Int],
74+
visitedLinks: Set[Url],
75+
inProgress: Set[Url],
76+
workers: Map[Host, BlockingQueue[Url]]
77+
)
78+
79+
sealed trait CrawlerMessage
80+
case class Start(url: Url) extends CrawlerMessage
81+
case class CrawlResult(url: Url, links: List[Url]) extends CrawlerMessage
82+
end Crawler
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package ox.crawler
2+
3+
type Host = String
4+
case class Url(host: Host, path: String)
5+
6+
trait Http:
7+
def get(url: Url): String

0 commit comments

Comments
 (0)