Skip to content

Commit b7225ae

Browse files
committed
WIP
1 parent ff0d504 commit b7225ae

File tree

12 files changed

+168
-14
lines changed

12 files changed

+168
-14
lines changed

core/src/main/kotlin/by/jprof/telegram/bot/core/UpdateProcessingPipeline.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ package by.jprof.telegram.bot.core
22

33
import dev.inmo.tgbotapi.types.update.abstracts.Update
44
import kotlinx.coroutines.CoroutineExceptionHandler
5+
import kotlinx.coroutines.async
6+
import kotlinx.coroutines.awaitAll
57
import kotlinx.coroutines.joinAll
6-
import kotlinx.coroutines.launch
78
import kotlinx.coroutines.runBlocking
89
import kotlinx.coroutines.supervisorScope
910
import org.apache.logging.log4j.LogManager

eval/dto/src/main/kotlin/by/jprof/telegram/bot/eval/dto/EvalResponse.kt

+11-5
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,14 @@ package by.jprof.telegram.bot.eval.dto
33
import kotlinx.serialization.Serializable
44

55
@Serializable
6-
data class EvalResponse(
7-
val language: Language,
8-
val stdout: String? = null,
9-
val stderr: String? = null,
10-
)
6+
sealed class EvalResponse {
7+
@Serializable
8+
data class Successful(
9+
val language: Language,
10+
val stdout: String? = null,
11+
val stderr: String? = null,
12+
) : EvalResponse()
13+
14+
@Serializable
15+
object Unsuccessful : EvalResponse()
16+
}

eval/dto/src/main/kotlin/by/jprof/telegram/bot/eval/dto/Language.kt

-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package by.jprof.telegram.bot.eval.dto
22

33
enum class Language {
4-
UNKNOWN,
54
KOTLIN,
65
JAVA,
76
JAVASCRIPT,

eval/evaluator/Dockerfile

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
FROM amazon/aws-lambda-java:latest
22

3-
COPY build/libs/jprof_by_bot-eval-evaluator-all.jar ${LAMBDA_TASK_ROOT}/lib/
4-
53
RUN yum -y install unzip tar gzip xz && \
64
yum -y clean all && \
75
rm -rf /var/cache
@@ -34,4 +32,5 @@ RUN curl -L https://golang.org/dl/go1.17.2.linux-amd64.tar.gz --output /tmp/go.t
3432
tar -xf /tmp/go.tar.gz -C /opt/go --strip-components 1 && \
3533
rm /tmp/go.tar.gz
3634

35+
COPY build/libs/jprof_by_bot-eval-evaluator-all.jar ${LAMBDA_TASK_ROOT}/lib/
3736
CMD [ "by.jprof.telegram.bot.eval.evaluator.Evaluator::handleRequest" ]

eval/evaluator/build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ dependencies {
77
api(project.projects.eval.dto)
88
implementation(libs.bundles.aws.lambda)
99
implementation(libs.koin.core)
10+
implementation(libs.kotlinx.coroutines.core)
1011
implementation(libs.kotlinx.serialization.json)
1112
implementation(libs.bundles.log4j)
1213

eval/evaluator/src/main/kotlin/by/jprof/telegram/bot/eval/evaluator/Evaluator.kt

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package by.jprof.telegram.bot.eval.evaluator
22

33
import by.jprof.telegram.bot.eval.dto.EvalEvent
4-
import by.jprof.telegram.bot.eval.dto.EvalResponse
5-
import by.jprof.telegram.bot.eval.dto.Language
64
import by.jprof.telegram.bot.eval.evaluator.config.jsonModule
5+
import by.jprof.telegram.bot.eval.evaluator.config.pipelineModule
6+
import by.jprof.telegram.bot.eval.evaluator.middleware.EvalPipeline
77
import com.amazonaws.services.lambda.runtime.Context
88
import com.amazonaws.services.lambda.runtime.RequestStreamHandler
9+
import kotlinx.coroutines.runBlocking
910
import kotlinx.serialization.ExperimentalSerializationApi
1011
import kotlinx.serialization.decodeFromString
1112
import kotlinx.serialization.json.Json
@@ -27,14 +28,16 @@ class Evaluator : RequestStreamHandler, KoinComponent {
2728
init {
2829
startKoin {
2930
modules(
30-
jsonModule
31+
jsonModule,
32+
pipelineModule,
3133
)
3234
}
3335
}
3436

3537
private val json: Json by inject()
38+
private val pipeline: EvalPipeline by inject()
3639

37-
override fun handleRequest(input: InputStream, output: OutputStream, context: Context) {
40+
override fun handleRequest(input: InputStream, output: OutputStream, context: Context) = runBlocking {
3841
val payload = input.bufferedReader().use { it.readText() }
3942

4043
logger.debug("Payload: {}", payload)
@@ -43,7 +46,7 @@ class Evaluator : RequestStreamHandler, KoinComponent {
4346

4447
logger.debug("Parsed event: {}", evalEvent)
4548

46-
val evalResponse = EvalResponse(Language.UNKNOWN)
49+
val evalResponse = pipeline.process(evalEvent)
4750

4851
output.buffered().use { json.encodeToStream(evalResponse, it) }
4952
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package by.jprof.telegram.bot.eval.evaluator.config
2+
3+
import by.jprof.telegram.bot.eval.evaluator.middleware.Eval
4+
import by.jprof.telegram.bot.eval.evaluator.middleware.EvalPipeline
5+
import by.jprof.telegram.bot.eval.evaluator.middleware.JavaScriptEval
6+
import org.koin.core.qualifier.named
7+
import org.koin.dsl.module
8+
9+
val pipelineModule = module {
10+
single {
11+
EvalPipeline(getAll())
12+
}
13+
14+
single<Eval>(named("JavaScriptEval")) {
15+
JavaScriptEval()
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package by.jprof.telegram.bot.eval.evaluator.middleware
2+
3+
import by.jprof.telegram.bot.eval.dto.EvalEvent
4+
import by.jprof.telegram.bot.eval.dto.EvalResponse
5+
6+
interface Eval {
7+
suspend fun eval(payload: EvalEvent): EvalResponse?
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package by.jprof.telegram.bot.eval.evaluator.middleware
2+
3+
import by.jprof.telegram.bot.eval.dto.EvalEvent
4+
import by.jprof.telegram.bot.eval.dto.EvalResponse
5+
import kotlinx.coroutines.CoroutineExceptionHandler
6+
import kotlinx.coroutines.async
7+
import kotlinx.coroutines.awaitAll
8+
import kotlinx.coroutines.runBlocking
9+
import kotlinx.coroutines.supervisorScope
10+
import org.apache.logging.log4j.LogManager
11+
12+
class EvalPipeline(
13+
private val evals: List<Eval>
14+
) {
15+
companion object {
16+
private val logger = LogManager.getLogger(EvalPipeline::class.java)!!
17+
}
18+
19+
fun process(evalEvent: EvalEvent): EvalResponse = runBlocking {
20+
supervisorScope {
21+
evals
22+
.map { async(exceptionHandler(it)) { it.eval(evalEvent) } }
23+
.awaitAll()
24+
.filterNotNull()
25+
.firstOrNull() ?: EvalResponse.Unsuccessful
26+
}
27+
}
28+
29+
private fun exceptionHandler(eval: Eval) = CoroutineExceptionHandler { _, exception ->
30+
logger.error("{} failed!", eval::class.simpleName, exception)
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package by.jprof.telegram.bot.eval.evaluator.middleware
2+
3+
import by.jprof.telegram.bot.eval.dto.EvalEvent
4+
import by.jprof.telegram.bot.eval.dto.EvalResponse
5+
import by.jprof.telegram.bot.eval.dto.Language
6+
import org.apache.logging.log4j.LogManager
7+
import java.io.IOException
8+
import java.util.concurrent.TimeUnit
9+
import kotlin.io.path.absolutePathString
10+
import kotlin.io.path.writeText
11+
12+
class JavaScriptEval : Eval {
13+
companion object {
14+
private val logger = LogManager.getLogger(JavaScriptEval::class.java)
15+
}
16+
17+
override suspend fun eval(payload: EvalEvent): EvalResponse? {
18+
val file = kotlin.io.path.createTempFile(prefix = "JavaScriptEval", suffix = ".js")
19+
20+
logger.info("Created temp file: {}", file)
21+
22+
file.writeText(payload.code)
23+
24+
try {
25+
val proc = ProcessBuilder("node", file.absolutePathString())
26+
.directory(file.parent.toFile())
27+
.redirectOutput(ProcessBuilder.Redirect.PIPE)
28+
.redirectError(ProcessBuilder.Redirect.PIPE)
29+
.start()
30+
31+
proc.waitFor(30, TimeUnit.SECONDS)
32+
33+
val result = proc.exitValue()
34+
val stdout = proc.inputStream.bufferedReader().use { it.readText() }
35+
val stderr = proc.errorStream.bufferedReader().use { it.readText() }
36+
37+
logger.info("Process finished with status: {}. stdout: {}, stderr: {}", result, stdout, stderr)
38+
39+
return EvalResponse.Successful(Language.JAVASCRIPT, stdout, stderr)
40+
} catch (e: IOException) {
41+
e.printStackTrace()
42+
return null
43+
}
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package by.jprof.telegram.bot.eval.evaluator.middleware
2+
3+
import by.jprof.telegram.bot.eval.dto.EvalEvent
4+
import by.jprof.telegram.bot.eval.dto.EvalResponse
5+
import kotlinx.coroutines.delay
6+
import org.junit.jupiter.api.Assertions
7+
import org.junit.jupiter.api.Test
8+
import java.time.Duration
9+
10+
internal class EvalPipelineTest {
11+
@Test
12+
fun processWithBroken() {
13+
val sut = EvalPipeline(
14+
(1..5).map { index ->
15+
when (index % 2) {
16+
0 -> Delay((index + 1) * 1000L)
17+
else -> Fail()
18+
}
19+
}
20+
)
21+
22+
Assertions.assertTimeout(Duration.ofMillis(6000)) {
23+
sut.process(EvalEvent(""))
24+
}
25+
}
26+
}
27+
28+
internal class Delay(
29+
private val delay: Long,
30+
) : Eval {
31+
override suspend fun eval(payload: EvalEvent): EvalResponse? {
32+
delay(delay)
33+
34+
return EvalResponse.Unsuccessful
35+
}
36+
}
37+
38+
internal class Fail : Eval {
39+
override suspend fun eval(payload: EvalEvent): EvalResponse? {
40+
throw IllegalArgumentException()
41+
}
42+
}

gradle/libs.versions.toml

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ aws-junit5 = "6.0.1"
2626
mockk = "1.12.0"
2727

2828
[libraries]
29+
kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" }
2930
kotlinx-coroutines-jdk8 = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-jdk8", version.ref = "coroutines" }
3031

3132
aws-lambda-java-events = { group = "com.amazonaws", name = "aws-lambda-java-events", version.ref = "aws-lambda-java-events" }

0 commit comments

Comments
 (0)