Fix concurrent TCP CLI scope and add regressions
This commit is contained in:
1 parent
c9eb3df93d
commit
fbb1683ba3
10 files changed
+471
-61
No files matched your search
@@ -24,6 +24,7 @@ import net.sergeych.lyng.bridge.bind
|
||||
import net.sergeych.lyng.bridge.bindObject
|
||||
import net.sergeych.lyng.bytecode.CmdFunction
|
||||
import net.sergeych.lyng.bytecode.CmdVm
|
||||
import net.sergeych.lyng.bytecode.BytecodeLambdaCallable
|
||||
import net.sergeych.lyng.miniast.*
|
||||
import net.sergeych.lyng.obj.*
|
||||
import net.sergeych.lyng.pacman.ImportManager
|
||||
@@ -635,16 +636,20 @@ class Script(
|
||||
addConst("MapEntry", ObjMapEntry.type)
|
||||
|
||||
addFn("launch") {
|
||||
val callable = requireOnlyArg<Obj>()
|
||||
val captured = this
|
||||
val rawCallable = requireOnlyArg<Obj>()
|
||||
val currentScope = requireScope()
|
||||
// Freeze non-module lexical state at launch time so each coroutine gets
|
||||
// its own view of loop locals and other transient frame values.
|
||||
val captured = if (currentScope is ModuleScope) currentScope else currentScope.snapshotForClosure()
|
||||
val callable = (rawCallable as? BytecodeLambdaCallable)?.freezeForLaunch(captured) ?: rawCallable
|
||||
val session = EvalSession.currentOrNull()
|
||||
val deferred = if (session != null) {
|
||||
session.launchTrackedDeferred {
|
||||
captured.call(callable)
|
||||
ScopeBridge(captured).call(callable)
|
||||
}
|
||||
} else {
|
||||
globalDefer {
|
||||
captured.call(callable)
|
||||
ScopeBridge(captured).call(callable)
|
||||
}
|
||||
}
|
||||
ObjDeferred(deferred)
|
||||
|
||||
@@ -1034,15 +1034,18 @@ class BytecodeCompiler(
|
||||
val typeValue = compileRefWithFallback(ref.castTypeRef(), null, ref.castPos()) ?: return null
|
||||
val objValue = ensureObjSlot(value)
|
||||
val typeObj = ensureObjSlot(typeValue)
|
||||
val sourceSlot = allocSlot()
|
||||
builder.emit(Opcode.MOVE_OBJ, objValue.slot, sourceSlot)
|
||||
updateSlotType(sourceSlot, SlotType.OBJ)
|
||||
if (!ref.castIsNullable()) {
|
||||
builder.emit(Opcode.ASSERT_IS, objValue.slot, typeObj.slot)
|
||||
builder.emit(Opcode.ASSERT_IS, sourceSlot, typeObj.slot)
|
||||
val resultSlot = allocSlot()
|
||||
builder.emit(Opcode.MAKE_QUALIFIED_VIEW, objValue.slot, typeObj.slot, resultSlot)
|
||||
builder.emit(Opcode.MAKE_QUALIFIED_VIEW, sourceSlot, typeObj.slot, resultSlot)
|
||||
updateSlotType(resultSlot, SlotType.OBJ)
|
||||
return CompiledValue(resultSlot, SlotType.OBJ)
|
||||
}
|
||||
val checkSlot = allocSlot()
|
||||
builder.emit(Opcode.CHECK_IS, objValue.slot, typeObj.slot, checkSlot)
|
||||
builder.emit(Opcode.CHECK_IS, sourceSlot, typeObj.slot, checkSlot)
|
||||
updateSlotType(checkSlot, SlotType.BOOL)
|
||||
val resultSlot = allocSlot()
|
||||
val nullSlot = allocSlot()
|
||||
@@ -1056,7 +1059,7 @@ class BytecodeCompiler(
|
||||
builder.emit(Opcode.MOVE_OBJ, nullSlot, resultSlot)
|
||||
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
|
||||
builder.mark(okLabel)
|
||||
builder.emit(Opcode.MAKE_QUALIFIED_VIEW, objValue.slot, typeObj.slot, resultSlot)
|
||||
builder.emit(Opcode.MAKE_QUALIFIED_VIEW, sourceSlot, typeObj.slot, resultSlot)
|
||||
builder.mark(endLabel)
|
||||
updateSlotType(resultSlot, SlotType.OBJ)
|
||||
return CompiledValue(resultSlot, SlotType.OBJ)
|
||||
|
||||
@@ -3783,11 +3783,48 @@ class BytecodeLambdaCallable(
|
||||
private val returnLabels: Set<String>,
|
||||
override val pos: Pos,
|
||||
) : Statement(), BytecodeCallable {
|
||||
private fun freezeRecord(record: ObjRecord): ObjRecord {
|
||||
val frozenValue = when (val raw = record.value) {
|
||||
is net.sergeych.lyng.FrameSlotRef -> raw.read()
|
||||
is net.sergeych.lyng.RecordSlotRef -> raw.read()
|
||||
is net.sergeych.lyng.ScopeSlotRef -> raw.read()
|
||||
else -> raw
|
||||
}
|
||||
return record.copy(value = frozenValue)
|
||||
}
|
||||
|
||||
private fun resolveCaptureRecords(base: Scope): List<ObjRecord>? {
|
||||
if (captureNames.isEmpty()) return null
|
||||
return captureNames.map { name ->
|
||||
base.chainLookupIgnoreClosure(
|
||||
name,
|
||||
followClosure = true,
|
||||
caller = base.currentClassCtx
|
||||
) ?: base.raiseSymbolNotFound("symbol $name not found")
|
||||
}
|
||||
}
|
||||
|
||||
fun rebindClosure(newClosureScope: Scope): BytecodeLambdaCallable {
|
||||
return BytecodeLambdaCallable(
|
||||
fn = fn,
|
||||
closureScope = newClosureScope,
|
||||
captureRecords = captureRecords,
|
||||
captureRecords = resolveCaptureRecords(newClosureScope) ?: captureRecords,
|
||||
captureNames = captureNames,
|
||||
paramSlotPlan = paramSlotPlan,
|
||||
argsDeclaration = argsDeclaration,
|
||||
preferredThisType = preferredThisType,
|
||||
returnLabels = returnLabels,
|
||||
pos = pos
|
||||
)
|
||||
}
|
||||
|
||||
fun freezeForLaunch(newClosureScope: Scope): BytecodeLambdaCallable {
|
||||
val frozenCaptures = captureRecords?.map(::freezeRecord)
|
||||
?: resolveCaptureRecords(newClosureScope)?.map(::freezeRecord)
|
||||
return BytecodeLambdaCallable(
|
||||
fn = fn,
|
||||
closureScope = newClosureScope,
|
||||
captureRecords = frozenCaptures,
|
||||
captureNames = captureNames,
|
||||
paramSlotPlan = paramSlotPlan,
|
||||
argsDeclaration = argsDeclaration,
|
||||
|
||||
@@ -157,4 +157,10 @@ class ImportManager(
|
||||
}
|
||||
}
|
||||
|
||||
fun invalidatePackageCache(name: String) {
|
||||
op.withLock {
|
||||
imports[name]?.cachedScope = null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -139,6 +139,66 @@ class TestCoroutines {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLaunchCapturesDistinctLoopValues() = runTest {
|
||||
eval(
|
||||
"""
|
||||
val jobs = []
|
||||
for( i in 0..<8 ) {
|
||||
val x = i
|
||||
jobs += launch {
|
||||
delay(5)
|
||||
x
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals([0,1,2,3,4,5,6,7], jobs.joinAll())
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNestedLaunchCapturesDistinctLoopValues() = runTest {
|
||||
eval(
|
||||
"""
|
||||
val outer = launch {
|
||||
val jobs = []
|
||||
for( i in 0..<8 ) {
|
||||
val x = i
|
||||
jobs += launch {
|
||||
delay(5)
|
||||
x
|
||||
}
|
||||
}
|
||||
jobs.joinAll()
|
||||
}
|
||||
|
||||
assertEquals([0,1,2,3,4,5,6,7], outer.await())
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLaunchCapturesDistinctObjectValues() = runTest {
|
||||
eval(
|
||||
"""
|
||||
val jobs = []
|
||||
for( i in 0..<8 ) {
|
||||
val box = ["item:${'$'}i"]
|
||||
jobs += launch {
|
||||
delay(5)
|
||||
box[0]
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
["item:0", "item:1", "item:2", "item:3", "item:4", "item:5", "item:6", "item:7"],
|
||||
jobs.joinAll()
|
||||
)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFlows() = runTest {
|
||||
eval("""
|
||||
|
||||
Reference in new issue
Block a user