Skip to content

Commit

Permalink
Merge pull request #441 from JetBrains/rd-call-fix-usov
Browse files Browse the repository at this point in the history
ensure counterpart is not called with terminated lifetime
  • Loading branch information
Iliya-usov authored Oct 6, 2023
2 parents 941e40e + 53d93ee commit 7634b15
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -365,12 +365,15 @@ class RdCall<TReq, TRes>(internal val requestSzr: ISerializer<TReq> = Polymorphi

val taskId = proto.identity.next(RdId.Null)
val bindLifetime = bindLifetime
val task = CallSiteWiredRdTask(lifetime.intersect(bindLifetime), this, taskId, scheduler ?: proto.scheduler)

proto.wire.send(rdid) { buffer ->
logSend.trace { "call `$location`::($rdid) send${sync.condstr {" SYNC"}} request '$taskId' : ${request.printToString()} " }
taskId.write(buffer)
requestSzr.write(ctx, buffer, request)
val taskLifetime = lifetime.intersect(bindLifetime)

val task = CallSiteWiredRdTask(taskLifetime, this, taskId, scheduler ?: proto.scheduler)
taskLifetime.executeIfAlive {
proto.wire.send(rdid) { buffer ->
logSend.trace { "call `$location`::($rdid) send${sync.condstr {" SYNC"}} request '$taskId' : ${request.printToString()} " }
taskId.write(buffer)
requestSzr.write(ctx, buffer, request)
}
}

return task
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ package com.jetbrains.rd.framework.test.cases
import com.jetbrains.rd.framework.RdTaskResult
import com.jetbrains.rd.framework.base.static
import com.jetbrains.rd.framework.impl.RdCall
import com.jetbrains.rd.framework.impl.RdTask
import com.jetbrains.rd.framework.isFaulted
import com.jetbrains.rd.framework.test.util.RdFrameworkTestBase
import com.jetbrains.rd.util.lifetime.Lifetime
import com.jetbrains.rd.util.lifetime.isAlive
import com.jetbrains.rd.util.lifetime.waitTermination
import com.jetbrains.rd.util.reactive.valueOrThrow
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFails
import kotlin.test.assertTrue
import kotlin.test.*

class RdTaskTest : RdFrameworkTestBase() {
@Test
Expand Down Expand Up @@ -59,6 +60,54 @@ class RdTaskTest : RdFrameworkTestBase() {
assertEquals("Cancelled", RdTaskResult.Cancelled<Int>().toString())
assertEquals("Fault :: com.jetbrains.rd.util.reactive.RdFault: error", RdTaskResult.Fault<Int>(Error("error")).toString())
}

@Test
fun startWithTerminatedLifetime() {
val entity_id = 1

val client_entity = RdCall<Int, String>().static(entity_id)
val server_entity = RdCall<Int, String>().static(entity_id)

clientProtocol.bindStatic(client_entity, "top")
serverProtocol.bindStatic(server_entity, "top")

var called = false
server_entity.set { lf, value ->
called = true
error("Must not be reached")
}

val task = client_entity.start(Lifetime.Terminated, 1)
val result = task.result.valueOrThrow
assertIs<RdTaskResult.Cancelled<String>>(result)
assertFalse(called)
}


@Test
fun startWithTerminatingDuringSet() {
val entity_id = 1

val client_entity = RdCall<Int, String>().static(entity_id)
val server_entity = RdCall<Int, String>().static(entity_id)

clientProtocol.bindStatic(client_entity, "top")
serverProtocol.bindStatic(server_entity, "top")

val def = clientLifetime.createNested()
var callLifetime: Lifetime? = null
server_entity.set { lf, value ->
def.terminate(true)

callLifetime = lf
RdTask.fromResult(value.toString())
}

val task = client_entity.start(def, 1)
val result = task.result.valueOrThrow
assertIs<RdTaskResult.Cancelled<String>>(result)
assertFalse(callLifetime!!.isAlive)
}
}

open class A(open val a:Any) {}
Expand Down
20 changes: 13 additions & 7 deletions rd-net/RdFramework/Tasks/RdCall.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,15 +199,21 @@ private IRdTask<TRes> StartInternal(Lifetime requestLifetime, TReq request, ISch
return RunHandler(request, requestLifetime, moniker: this);

var taskId = proto.Identities.Next(RdId.Nil);
var task = new WiredRdTask<TReq,TRes>.CallSite(Lifetime.Intersect(requestLifetime, myBindLifetime), this, taskId, scheduler ?? proto.Scheduler);

proto.Wire.Send(RdId, (writer) =>

var taskLifetime = Lifetime.Intersect(requestLifetime, myBindLifetime);
var task = new WiredRdTask<TReq, TRes>.CallSite(taskLifetime, this, taskId, scheduler ?? proto.Scheduler);

using var cookie = taskLifetime.UsingExecuteIfAlive();
if (cookie.Succeed)
{
SendTrace?.Log($"{task} :: send request: {request.PrintToString()}");
proto.Wire.Send(RdId, (writer) =>
{
SendTrace?.Log($"{task} :: send request: {request.PrintToString()}");
taskId.Write(writer);
WriteRequestDelegate(serializationContext, writer, request);
});
taskId.Write(writer);
WriteRequestDelegate(serializationContext, writer, request);
});
}

return task;
}
Expand Down
53 changes: 53 additions & 0 deletions rd-net/Test.RdFramework/RdTaskTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -241,5 +241,58 @@ public void TestOverriddenHandlerScheduler()

Assert.AreEqual("0", result.Value.Result);
}

[Test]
public void StartWithTerminatedLifetime()
{
ClientWire.AutoTransmitMode = true;
ServerWire.AutoTransmitMode = true;
var entity_id = 1;

Check warning on line 250 in rd-net/Test.RdFramework/RdTaskTest.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'entity_id' is assigned but its value is never used

Check warning on line 250 in rd-net/Test.RdFramework/RdTaskTest.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'entity_id' is assigned but its value is never used

Check warning on line 250 in rd-net/Test.RdFramework/RdTaskTest.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'entity_id' is assigned but its value is never used

Check warning on line 250 in rd-net/Test.RdFramework/RdTaskTest.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'entity_id' is assigned but its value is never used

var serverEntity = BindToServer(LifetimeDefinition.Lifetime, NewRdCall<int, string>(), ourKey);
var clientEntity = BindToClient(LifetimeDefinition.Lifetime, NewRdCall<int, string>(), ourKey);

var called = false;
serverEntity.Set((lf, value) =>

Check warning on line 256 in rd-net/Test.RdFramework/RdTaskTest.cs

View workflow job for this annotation

GitHub Actions / build

'RdCall<int, string>.Set(Func<Lifetime, int, RdTask<string>>, IScheduler?, IScheduler?)' is obsolete: 'This is an internal API. It is preferable to use SetSync or SetAsync extension methods'

Check warning on line 256 in rd-net/Test.RdFramework/RdTaskTest.cs

View workflow job for this annotation

GitHub Actions / build

'RdCall<int, string>.Set(Func<Lifetime, int, RdTask<string>>, IScheduler?, IScheduler?)' is obsolete: 'This is an internal API. It is preferable to use SetSync or SetAsync extension methods'

Check warning on line 256 in rd-net/Test.RdFramework/RdTaskTest.cs

View workflow job for this annotation

GitHub Actions / build

'RdCall<int, string>.Set(Func<Lifetime, int, RdTask<string>>, IScheduler?, IScheduler?)' is obsolete: 'This is an internal API. It is preferable to use SetSync or SetAsync extension methods'

Check warning on line 256 in rd-net/Test.RdFramework/RdTaskTest.cs

View workflow job for this annotation

GitHub Actions / build

'RdCall<int, string>.Set(Func<Lifetime, int, RdTask<string>>, IScheduler?, IScheduler?)' is obsolete: 'This is an internal API. It is preferable to use SetSync or SetAsync extension methods'
{
called = true;
throw new InvalidOperationException("Must not be reached");
});

var task = clientEntity.Start(Lifetime.Terminated, 1);
var result = task.Result.Value;
Assert.IsTrue(result.Status == RdTaskStatus.Canceled);
Assert.IsFalse(called);
}


[Test]
public void StartWithTerminatingDuringSet()
{
ClientWire.AutoTransmitMode = true;
ServerWire.AutoTransmitMode = true;
var entity_id = 1;

Check warning on line 274 in rd-net/Test.RdFramework/RdTaskTest.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'entity_id' is assigned but its value is never used

Check warning on line 274 in rd-net/Test.RdFramework/RdTaskTest.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'entity_id' is assigned but its value is never used

var serverEntity = BindToServer(LifetimeDefinition.Lifetime, NewRdCall<int, string>(), ourKey);
var clientEntity = BindToClient(LifetimeDefinition.Lifetime, NewRdCall<int, string>(), ourKey);

var def = TestLifetime.CreateNested();
Lifetime callLifetime = default;
serverEntity.Set((lf, value) =>

Check warning on line 281 in rd-net/Test.RdFramework/RdTaskTest.cs

View workflow job for this annotation

GitHub Actions / build

'RdCall<int, string>.Set(Func<Lifetime, int, RdTask<string>>, IScheduler?, IScheduler?)' is obsolete: 'This is an internal API. It is preferable to use SetSync or SetAsync extension methods'

Check warning on line 281 in rd-net/Test.RdFramework/RdTaskTest.cs

View workflow job for this annotation

GitHub Actions / build

'RdCall<int, string>.Set(Func<Lifetime, int, RdTask<string>>, IScheduler?, IScheduler?)' is obsolete: 'This is an internal API. It is preferable to use SetSync or SetAsync extension methods'

Check warning on line 281 in rd-net/Test.RdFramework/RdTaskTest.cs

View workflow job for this annotation

GitHub Actions / build

'RdCall<int, string>.Set(Func<Lifetime, int, RdTask<string>>, IScheduler?, IScheduler?)' is obsolete: 'This is an internal API. It is preferable to use SetSync or SetAsync extension methods'
{
using (new LifetimeDefinition.AllowTerminationUnderExecutionCookie(Thread.CurrentThread))
{
def.Terminate();
}
callLifetime = lf;
return RdTask.Successful(value.ToString());
});

var task = clientEntity.Start(def.Lifetime, 1);
var result = task.Result.Value;
Assert.IsTrue(result.Status == RdTaskStatus.Canceled);
Assert.IsFalse(callLifetime.IsAlive);
}
}
}

0 comments on commit 7634b15

Please sign in to comment.