- Add codegen for events
- Add ServiceHost logic
- Add relevant tests
- Add event example
This commit is contained in:
Eduardo Salinas 2016-04-20 12:56:11 -07:00 коммит произвёл Christopher Warrington
Родитель 0a61dd43b2
Коммит 87d1f2480f
19 изменённых файлов: 682 добавлений и 78 удалений

Просмотреть файл

@ -52,7 +52,11 @@ namespace #{csNamespace}
where
getMessageResultTypeName = getMessageTypeName cs methodResult
getMessageInputTypeName = getMessageTypeName cs methodInput
methodDeclaration _ = mempty
methodDeclaration Event{..} = [lt|void #{methodName}Async(global::Bond.Comm.IMessage<#{getMessageInputTypeName}> param);|]
where
getMessageInputTypeName = getMessageTypeName cs methodInput
comm _ = mempty
comm_proxy_cs :: MappingContext -> String -> [Import] -> [Declaration] -> (String, L.Text)
@ -81,7 +85,7 @@ namespace #{csNamespace}
}|]
where
methodCapability Function {} = "global::Bond.Comm.IRequestResponseConnection"
methodCapability _ = ""
methodCapability Event {} = "global::Bond.Comm.IEventConnection"
getCapabilities :: [Method] -> [String]
getCapabilities m = nub $ map methodCapability m
@ -106,7 +110,21 @@ namespace #{csNamespace}
where
getMessageResultTypeName = getMessageTypeName cs methodResult
getMessageInputTypeName = getMessageTypeName cs methodInput
proxyMethod _ = mempty
proxyMethod Event{..} = [lt|public void #{methodName}Async(#{getMessageInputTypeName} param)
{
var message = new global::Bond.Comm.Message<#{getMessageInputTypeName}>(param);
#{methodName}Async(message);
}
public void #{methodName}Async(global::Bond.Comm.IMessage<#{getMessageInputTypeName}> param)
{
m_connection.FireEventAsync<#{getMessageInputTypeName}>(
"#{getDeclTypeName idl s}.#{methodName}",
param);
}|]
where
getMessageInputTypeName = getMessageTypeName cs methodInput
comm _ = mempty
@ -141,14 +159,17 @@ namespace #{csNamespace}
where
generics = angles $ sepBy ", " paramName declParams
methodInfo Function{..} = [lt|yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="#{getDeclTypeName idl s}.#{methodName}", Callback = #{methodName}Async_Glue};|]
methodInfo _ = mempty
methodInfo Function{..} = [lt|yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="#{getDeclTypeName idl s}.#{methodName}", Callback = #{methodName}Async_Glue, CallbackType = global::Bond.Comm.ServiceCallbackType.RequestResponse};|]
methodInfo Event{..} = [lt|yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="#{getDeclTypeName idl s}.#{methodName}", Callback = #{methodName}Async_Glue, CallbackType = global::Bond.Comm.ServiceCallbackType.Event};|]
methodAbstract Function{..} = [lt|public abstract global::System.Threading.Tasks.Task<global::Bond.Comm.IMessage<#{getMessageResultTypeName}>> #{methodName}Async(global::Bond.Comm.IMessage<#{getMessageInputTypeName}> param, global::System.Threading.CancellationToken ct);|]
where
getMessageResultTypeName = getMessageTypeName cs methodResult
getMessageInputTypeName = getMessageTypeName cs methodInput
methodAbstract _ = mempty
methodAbstract Event{..} = [lt|public abstract void #{methodName}Async(global::Bond.Comm.IMessage<#{getMessageInputTypeName}> param);|]
where
getMessageInputTypeName = getMessageTypeName cs methodInput
methodGlue Function{..} = [lt|private global::System.Threading.Tasks.Task<global::Bond.Comm.IMessage> #{methodName}Async_Glue(global::Bond.Comm.IMessage param, global::Bond.Comm.ReceiveContext context, global::System.Threading.CancellationToken ct)
{
@ -159,5 +180,13 @@ namespace #{csNamespace}
where
getMessageResultTypeName = getMessageTypeName cs methodResult
getMessageInputTypeName = getMessageTypeName cs methodInput
methodGlue _ = mempty
methodGlue Event{..} = [lt|private global::System.Threading.Tasks.Task #{methodName}Async_Glue(global::Bond.Comm.IMessage param, global::Bond.Comm.ReceiveContext context, global::System.Threading.CancellationToken ct)
{
#{methodName}Async(param.Convert<#{getMessageInputTypeName}>());
return global::Bond.Comm.CodegenHelpers.CompletedTask;
}|]
where
getMessageInputTypeName = getMessageTypeName cs methodInput
comm _ = mempty

Просмотреть файл

@ -22,9 +22,9 @@ namespace tests
{
get
{
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo31", Callback = foo31Async_Glue};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo32", Callback = foo32Async_Glue};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo33", Callback = foo33Async_Glue};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo31", Callback = foo31Async_Glue, CallbackType = global::Bond.Comm.ServiceCallbackType.RequestResponse};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo32", Callback = foo32Async_Glue, CallbackType = global::Bond.Comm.ServiceCallbackType.RequestResponse};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo33", Callback = foo33Async_Glue, CallbackType = global::Bond.Comm.ServiceCallbackType.RequestResponse};
}
}

Просмотреть файл

@ -18,6 +18,14 @@ namespace tests
[System.CodeDom.Compiler.GeneratedCode("gbc", "0.4.0.2")]
interface IFoo
{
void foo11Async(global::Bond.Comm.IMessage<global::Bond.Void> param);
void foo12Async(global::Bond.Comm.IMessage<global::Bond.Void> param);
void foo13Async(global::Bond.Comm.IMessage<BasicTypes> param);
void foo14Async(global::Bond.Comm.IMessage<dummy> param);
global::System.Threading.Tasks.Task<global::Bond.Comm.IMessage<global::Bond.Void>> foo21Async(global::Bond.Comm.IMessage<global::Bond.Void> param, global::System.Threading.CancellationToken ct);
global::System.Threading.Tasks.Task<global::Bond.Comm.IMessage<global::Bond.Void>> foo22Async(global::Bond.Comm.IMessage<global::Bond.Void> param, global::System.Threading.CancellationToken ct);

Просмотреть файл

@ -16,7 +16,7 @@
namespace tests
{
[System.CodeDom.Compiler.GeneratedCode("gbc", "0.4.0.2")]
public class FooProxy<TConnection> : IFoo where TConnection : global::Bond.Comm.IRequestResponseConnection
public class FooProxy<TConnection> : IFoo where TConnection : global::Bond.Comm.IEventConnection, global::Bond.Comm.IRequestResponseConnection
{
private readonly TConnection m_connection;
@ -25,6 +25,58 @@ namespace tests
m_connection = connection;
}
public void foo11Async(global::Bond.Void param)
{
var message = new global::Bond.Comm.Message<global::Bond.Void>(param);
foo11Async(message);
}
public void foo11Async(global::Bond.Comm.IMessage<global::Bond.Void> param)
{
m_connection.FireEventAsync<global::Bond.Void>(
"tests.Foo.foo11",
param);
}
public void foo12Async(global::Bond.Void param)
{
var message = new global::Bond.Comm.Message<global::Bond.Void>(param);
foo12Async(message);
}
public void foo12Async(global::Bond.Comm.IMessage<global::Bond.Void> param)
{
m_connection.FireEventAsync<global::Bond.Void>(
"tests.Foo.foo12",
param);
}
public void foo13Async(BasicTypes param)
{
var message = new global::Bond.Comm.Message<BasicTypes>(param);
foo13Async(message);
}
public void foo13Async(global::Bond.Comm.IMessage<BasicTypes> param)
{
m_connection.FireEventAsync<BasicTypes>(
"tests.Foo.foo13",
param);
}
public void foo14Async(dummy param)
{
var message = new global::Bond.Comm.Message<dummy>(param);
foo14Async(message);
}
public void foo14Async(global::Bond.Comm.IMessage<dummy> param)
{
m_connection.FireEventAsync<dummy>(
"tests.Foo.foo14",
param);
}
public global::System.Threading.Tasks.Task<global::Bond.Comm.IMessage<global::Bond.Void>> foo21Async(global::Bond.Void param)
{
var message = new global::Bond.Comm.Message<global::Bond.Void>(param);

Просмотреть файл

@ -22,21 +22,33 @@ namespace tests
{
get
{
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo21", Callback = foo21Async_Glue};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo22", Callback = foo22Async_Glue};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo23", Callback = foo23Async_Glue};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo24", Callback = foo24Async_Glue};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo31", Callback = foo31Async_Glue};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo32", Callback = foo32Async_Glue};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo33", Callback = foo33Async_Glue};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo34", Callback = foo34Async_Glue};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo41", Callback = foo41Async_Glue};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo42", Callback = foo42Async_Glue};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo43", Callback = foo43Async_Glue};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo44", Callback = foo44Async_Glue};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo11", Callback = foo11Async_Glue, CallbackType = global::Bond.Comm.ServiceCallbackType.Event};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo12", Callback = foo12Async_Glue, CallbackType = global::Bond.Comm.ServiceCallbackType.Event};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo13", Callback = foo13Async_Glue, CallbackType = global::Bond.Comm.ServiceCallbackType.Event};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo14", Callback = foo14Async_Glue, CallbackType = global::Bond.Comm.ServiceCallbackType.Event};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo21", Callback = foo21Async_Glue, CallbackType = global::Bond.Comm.ServiceCallbackType.RequestResponse};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo22", Callback = foo22Async_Glue, CallbackType = global::Bond.Comm.ServiceCallbackType.RequestResponse};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo23", Callback = foo23Async_Glue, CallbackType = global::Bond.Comm.ServiceCallbackType.RequestResponse};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo24", Callback = foo24Async_Glue, CallbackType = global::Bond.Comm.ServiceCallbackType.RequestResponse};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo31", Callback = foo31Async_Glue, CallbackType = global::Bond.Comm.ServiceCallbackType.RequestResponse};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo32", Callback = foo32Async_Glue, CallbackType = global::Bond.Comm.ServiceCallbackType.RequestResponse};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo33", Callback = foo33Async_Glue, CallbackType = global::Bond.Comm.ServiceCallbackType.RequestResponse};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo34", Callback = foo34Async_Glue, CallbackType = global::Bond.Comm.ServiceCallbackType.RequestResponse};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo41", Callback = foo41Async_Glue, CallbackType = global::Bond.Comm.ServiceCallbackType.RequestResponse};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo42", Callback = foo42Async_Glue, CallbackType = global::Bond.Comm.ServiceCallbackType.RequestResponse};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo43", Callback = foo43Async_Glue, CallbackType = global::Bond.Comm.ServiceCallbackType.RequestResponse};
yield return new global::Bond.Comm.ServiceMethodInfo {MethodName="tests.Foo.foo44", Callback = foo44Async_Glue, CallbackType = global::Bond.Comm.ServiceCallbackType.RequestResponse};
}
}
public abstract void foo11Async(global::Bond.Comm.IMessage<global::Bond.Void> param);
public abstract void foo12Async(global::Bond.Comm.IMessage<global::Bond.Void> param);
public abstract void foo13Async(global::Bond.Comm.IMessage<BasicTypes> param);
public abstract void foo14Async(global::Bond.Comm.IMessage<dummy> param);
public abstract global::System.Threading.Tasks.Task<global::Bond.Comm.IMessage<global::Bond.Void>> foo21Async(global::Bond.Comm.IMessage<global::Bond.Void> param, global::System.Threading.CancellationToken ct);
public abstract global::System.Threading.Tasks.Task<global::Bond.Comm.IMessage<global::Bond.Void>> foo22Async(global::Bond.Comm.IMessage<global::Bond.Void> param, global::System.Threading.CancellationToken ct);
@ -61,6 +73,30 @@ namespace tests
public abstract global::System.Threading.Tasks.Task<global::Bond.Comm.IMessage<dummy>> foo44Async(global::Bond.Comm.IMessage<dummy> param, global::System.Threading.CancellationToken ct);
private global::System.Threading.Tasks.Task foo11Async_Glue(global::Bond.Comm.IMessage param, global::Bond.Comm.ReceiveContext context, global::System.Threading.CancellationToken ct)
{
foo11Async(param.Convert<global::Bond.Void>());
return global::Bond.Comm.CodegenHelpers.CompletedTask;
}
private global::System.Threading.Tasks.Task foo12Async_Glue(global::Bond.Comm.IMessage param, global::Bond.Comm.ReceiveContext context, global::System.Threading.CancellationToken ct)
{
foo12Async(param.Convert<global::Bond.Void>());
return global::Bond.Comm.CodegenHelpers.CompletedTask;
}
private global::System.Threading.Tasks.Task foo13Async_Glue(global::Bond.Comm.IMessage param, global::Bond.Comm.ReceiveContext context, global::System.Threading.CancellationToken ct)
{
foo13Async(param.Convert<BasicTypes>());
return global::Bond.Comm.CodegenHelpers.CompletedTask;
}
private global::System.Threading.Tasks.Task foo14Async_Glue(global::Bond.Comm.IMessage param, global::Bond.Comm.ReceiveContext context, global::System.Threading.CancellationToken ct)
{
foo14Async(param.Convert<dummy>());
return global::Bond.Comm.CodegenHelpers.CompletedTask;
}
private global::System.Threading.Tasks.Task<global::Bond.Comm.IMessage> foo21Async_Glue(global::Bond.Comm.IMessage param, global::Bond.Comm.ReceiveContext context, global::System.Threading.CancellationToken ct)
{
return global::Bond.Comm.CodegenHelpers.Upcast<global::Bond.Comm.IMessage<global::Bond.Void>,

Просмотреть файл

@ -202,6 +202,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "logging", "..\examples\cs\c
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "service", "src\comm\service\service.csproj", "{79D2423A-87C8-44A2-89C2-2FA94521747E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "notifyevent", "..\examples\cs\comm\notifyevent\notifyevent.csproj", "{12279366-F646-4FEC-8CAA-B62A8EC477BB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -779,6 +781,24 @@ Global
{79D2423A-87C8-44A2-89C2-2FA94521747E}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{79D2423A-87C8-44A2-89C2-2FA94521747E}.Release|Win32.ActiveCfg = Release|Any CPU
{79D2423A-87C8-44A2-89C2-2FA94521747E}.Release|Win32.Build.0 = Release|Any CPU
{12279366-F646-4FEC-8CAA-B62A8EC477BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{12279366-F646-4FEC-8CAA-B62A8EC477BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{12279366-F646-4FEC-8CAA-B62A8EC477BB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{12279366-F646-4FEC-8CAA-B62A8EC477BB}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{12279366-F646-4FEC-8CAA-B62A8EC477BB}.Debug|Win32.ActiveCfg = Debug|Any CPU
{12279366-F646-4FEC-8CAA-B62A8EC477BB}.Debug|Win32.Build.0 = Debug|Any CPU
{12279366-F646-4FEC-8CAA-B62A8EC477BB}.Fields|Any CPU.ActiveCfg = Debug|Any CPU
{12279366-F646-4FEC-8CAA-B62A8EC477BB}.Fields|Any CPU.Build.0 = Debug|Any CPU
{12279366-F646-4FEC-8CAA-B62A8EC477BB}.Fields|Mixed Platforms.ActiveCfg = Debug|Any CPU
{12279366-F646-4FEC-8CAA-B62A8EC477BB}.Fields|Mixed Platforms.Build.0 = Debug|Any CPU
{12279366-F646-4FEC-8CAA-B62A8EC477BB}.Fields|Win32.ActiveCfg = Debug|Any CPU
{12279366-F646-4FEC-8CAA-B62A8EC477BB}.Fields|Win32.Build.0 = Debug|Any CPU
{12279366-F646-4FEC-8CAA-B62A8EC477BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{12279366-F646-4FEC-8CAA-B62A8EC477BB}.Release|Any CPU.Build.0 = Release|Any CPU
{12279366-F646-4FEC-8CAA-B62A8EC477BB}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{12279366-F646-4FEC-8CAA-B62A8EC477BB}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{12279366-F646-4FEC-8CAA-B62A8EC477BB}.Release|Win32.ActiveCfg = Release|Any CPU
{12279366-F646-4FEC-8CAA-B62A8EC477BB}.Release|Win32.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -816,5 +836,6 @@ Global
{54A3432B-99E1-4DEB-B4EB-2D6E158ECD24} = {ED161076-6BB8-4A13-83ED-0E9C01461D5E}
{5C8132A8-C4B1-45E0-BCA6-379DA23B86D3} = {621A2166-EEE0-4A27-88AA-5BE5AC996452}
{79D2423A-87C8-44A2-89C2-2FA94521747E} = {ED161076-6BB8-4A13-83ED-0E9C01461D5E}
{12279366-F646-4FEC-8CAA-B62A8EC477BB} = {621A2166-EEE0-4A27-88AA-5BE5AC996452}
EndGlobalSection
EndGlobal

Просмотреть файл

@ -48,5 +48,18 @@ namespace Bond.Comm
return tcsOuter.Task;
}
private static Task s_completedTask = Task.FromResult(default(object));
/// <summary>
/// Returns a completed Task
/// </summary>
public static Task CompletedTask
{
get
{
return s_completedTask;
}
}
}
}

Просмотреть файл

@ -8,6 +8,7 @@ enum ErrorCode
OK = 0x0;
InternalServerError = 0xA0BD0002;
MethodNotFound = 0xA0BD0003;
InvalidInvocation = 0xA0BD0004;
}
struct Error

Просмотреть файл

@ -48,16 +48,13 @@ namespace Bond.Comm
/// invoke.
/// </param>
/// <param name="message">The message to send.</param>
/// <param name="ct">
/// The cancellation token for cooperative cancellation.
/// </param>
/// <returns>A task representing the asynchronous operation.</returns>
/// <remarks>
/// Event methods cannot send responses or error. However, the returned
/// task may represent an error if there was a local error sending the
/// message.
/// </remarks>
Task FireEventAsync<TPayload>(string methodName, IMessage<TPayload> message, CancellationToken ct);
Task FireEventAsync<TPayload>(string methodName, IMessage<TPayload> message);
}
/// <summary>

Просмотреть файл

@ -22,7 +22,23 @@ namespace Bond.Comm
/// This class is primarily used by Bond's generated service
/// implementations.
/// </remarks>
public delegate Task<IMessage> ServiceCallback(IMessage request, ReceiveContext context, CancellationToken ct);
public delegate Task ServiceCallback(IMessage request, ReceiveContext context, CancellationToken ct);
/// <summary>
/// Indicates the type of ServiceCallback
/// </summary>
public enum ServiceCallbackType
{
/// <summary>
/// Method returns a Task&lt;IMessage&gt;
/// </summary>
RequestResponse,
/// <summary>
/// Method returns a Task
/// </summary>
Event
};
/// <summary>
/// Data about a Bond service method.
@ -38,6 +54,7 @@ namespace Bond.Comm
/// The delegate to invoke for this method.
/// </summary>
public ServiceCallback Callback;
public ServiceCallbackType CallbackType;
}
/// <summary>

Просмотреть файл

@ -5,6 +5,7 @@ namespace Bond.Comm.Service
{
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Bond.Comm;
@ -13,14 +14,14 @@ namespace Bond.Comm.Service
{
private readonly Transport m_parentTransport;
private readonly object m_lock;
private readonly Dictionary<string, ServiceCallback> m_dispatchTable;
private readonly Dictionary<string, ServiceMethodInfo> m_dispatchTable;
public ServiceHost(Transport parentTransport)
{
m_parentTransport = parentTransport;
m_lock = new object();
m_dispatchTable = new Dictionary<string, ServiceCallback>();
m_dispatchTable = new Dictionary<string, ServiceMethodInfo>();
}
public bool IsRegistered(string serviceMethodName)
@ -31,9 +32,43 @@ namespace Bond.Comm.Service
}
}
public void ValidateServiceMethods(IEnumerable<ServiceMethodInfo> serviceMethods)
{
Type returnParameter;
foreach (var serviceMethod in serviceMethods)
{
switch (serviceMethod.CallbackType)
{
case ServiceCallbackType.RequestResponse:
returnParameter = serviceMethod.Callback.GetMethodInfo().ReturnType;
if (returnParameter != typeof(Task<IMessage>))
{
throw new ArgumentException($"{serviceMethod.MethodName} registered as " +
$"{serviceMethod.CallbackType} but callback not implemented as such.");
}
break;
case ServiceCallbackType.Event:
returnParameter = serviceMethod.Callback.GetMethodInfo().ReturnType;
if (returnParameter != typeof(Task))
{
throw new ArgumentException($"{serviceMethod.MethodName} registered as " +
$"{serviceMethod.CallbackType} but callback not implemented as such.");
}
break;
default:
throw new ArgumentException($"{serviceMethod.MethodName} registered as invalid type " +
$"{serviceMethod.CallbackType}.");
}
}
}
public void Register(IService service)
{
var methodNames = new SortedSet<string>();
ValidateServiceMethods(service.Methods);
lock (m_lock)
{
// Service methods are registerd as a unit - either register all or none.
@ -52,7 +87,7 @@ namespace Bond.Comm.Service
foreach (var serviceMethod in service.Methods)
{
m_dispatchTable.Add(serviceMethod.MethodName, serviceMethod.Callback);
m_dispatchTable.Add(serviceMethod.MethodName, serviceMethod);
methodNames.Add(serviceMethod.MethodName);
}
}
@ -78,54 +113,116 @@ namespace Bond.Comm.Service
{
Log.Information("{0}.{1}: Got request {2} from {3}.",
nameof(ServiceHost), nameof(DispatchRequest), methodName, context.Connection);
ServiceCallback callback;
IMessage result = null;
ServiceMethodInfo methodInfo;
lock (m_lock)
{
if (!m_dispatchTable.TryGetValue(methodName, out callback))
if (!m_dispatchTable.TryGetValue(methodName, out methodInfo))
{
var errorMessage = LogUtil.FatalAndReturnFormatted("{0}.{1}: Got request for unknown method {2}.",
nameof(ServiceHost), nameof(DispatchRequest), methodName);
var error = new Error()
var error = new Error
{
message = errorMessage,
error_code = (int)ErrorCode.MethodNotFound
};
result = Message.FromError(error);
return Message.FromError(error);
}
}
if (result == null)
if (methodInfo.CallbackType != ServiceCallbackType.RequestResponse)
{
var errorMessage = LogUtil.FatalAndReturnFormatted("{0}.{1}: Method {2} invoked as if it were {3}, but it was registered as {4}.",
nameof(ServiceHost), nameof(DispatchRequest), methodName, ServiceCallbackType.RequestResponse, methodInfo.CallbackType);
var error = new Error
{
message = errorMessage,
error_code = (int)ErrorCode.InvalidInvocation
};
return Message.FromError(error);
}
IMessage result = null;
try
{
// Cast to appropriate return type which we validated when registering the service
result = await (Task<IMessage>)methodInfo.Callback(message, context, CancellationToken.None);
}
catch (Exception callbackEx)
{
Error error = null;
try
{
result = await callback(message, context, CancellationToken.None);
error = m_parentTransport.UnhandledExceptionHandler(callbackEx);
}
catch (Exception callbackEx)
catch (Exception handlerEx)
{
Error error = null;
try
{
error = m_parentTransport.UnhandledExceptionHandler(callbackEx);
}
catch (Exception handlerEx)
{
Transport.FailFastExceptionHandler(handlerEx);
}
if (error == null)
{
Transport.FailFastExceptionHandler(callbackEx);
}
result = Message.FromError(error);
Transport.FailFastExceptionHandler(handlerEx);
}
if (error == null)
{
Transport.FailFastExceptionHandler(callbackEx);
}
result = Message.FromError(error);
}
return result;
}
public async Task DispatchEvent(string methodName, ReceiveContext context, IMessage message)
{
Log.Information("{0}.{1}: Got event {2} from {3}.",
nameof(ServiceHost), nameof(DispatchEvent), methodName, context.Connection);
ServiceMethodInfo methodInfo;
lock (m_lock)
{
if (!m_dispatchTable.TryGetValue(methodName, out methodInfo))
{
LogUtil.FatalAndReturnFormatted("{0}.{1}: Got request for unknown method {2}.",
nameof(ServiceHost), nameof(DispatchRequest), methodName);
return;
}
}
if (methodInfo.CallbackType != ServiceCallbackType.Event)
{
LogUtil.FatalAndReturnFormatted("{0}.{1}: Method {2} invoked as if it were {3}, but it was registered as {4}.",
nameof(ServiceHost), nameof(DispatchRequest), methodName, ServiceCallbackType.Event, methodInfo.CallbackType);
return;
}
try
{
await methodInfo.Callback(message, context, CancellationToken.None);
}
catch (Exception callbackEx)
{
Error error = null;
try
{
error = m_parentTransport.UnhandledExceptionHandler(callbackEx);
}
catch (Exception handlerEx)
{
Transport.FailFastExceptionHandler(handlerEx);
}
if (error == null)
{
Transport.FailFastExceptionHandler(callbackEx);
}
LogUtil.FatalAndReturnFormatted("{0}.{1}: Failed to complete method {2}. With exception: {3}",
nameof(ServiceHost), nameof(DispatchRequest), methodName, callbackEx.ToString());
}
}
}
}

Просмотреть файл

@ -20,7 +20,7 @@ namespace Bond.Comm.Tcp
Server
}
public class TcpConnection : Connection, IRequestResponseConnection
public class TcpConnection : Connection, IRequestResponseConnection, IEventConnection
{
private TcpTransport m_parentTransport;
@ -188,6 +188,25 @@ namespace Bond.Comm.Tcp
}
}
internal async Task SendEventAsync(string methodName, IMessage message)
{
uint requestId = AllocateNextRequestId();
var frame = MessageToFrame(requestId, methodName, PayloadType.Event, message);
Log.Debug("{0}.{1}: Sending event {2}/{3}.", this, nameof(SendEventAsync), requestId, methodName);
using (var binWriter = new BinaryWriter(m_networkStream, encoding: Encoding.UTF8, leaveOpen: true))
{
lock (m_networkStream)
{
frame.Write(binWriter);
binWriter.Flush();
}
}
await m_networkStream.FlushAsync();
Log.Debug("{0}.{1}: Sent event {2}/{3}.", this, nameof(SendEventAsync), requestId, methodName);
}
internal void Start()
{
Task.Run(() => ProcessFramesAsync(m_networkStream));
@ -221,6 +240,10 @@ namespace Bond.Comm.Tcp
DispatchResponse(result.Headers, result.Payload);
break;
case TcpProtocol.FrameDisposition.DeliverEventToService:
DispatchEvent(result.Headers, result.Payload);
break;
case TcpProtocol.FrameDisposition.SendProtocolError:
await SendProtocolErrorAsync(result.ErrorCode ?? ProtocolErrorCode.INTERNAL_ERROR);
break;
@ -278,6 +301,21 @@ namespace Bond.Comm.Tcp
responseCompletionSource.SetResult(response);
}
private void DispatchEvent(TcpHeaders headers, ArraySegment<byte> payload)
{
if (headers.error_code != (int)ErrorCode.OK)
{
throw new TcpProtocolErrorException("Received a request with non-zero error code. Request ID " + headers.request_id);
}
IMessage request = Message.FromPayload(Unmarshal.From(payload));
Task.Run(async () =>
{
await m_serviceHost.DispatchEvent(headers.method_name, new TcpReceiveContext(this), request);
});
}
public override Task StopAsync()
{
Log.Debug("{0}.{1}: Shutting down.", this, nameof(StopAsync));
@ -291,5 +329,10 @@ namespace Bond.Comm.Tcp
IMessage response = await SendRequestAsync(methodName, message);
return response.Convert<TResponse>();
}
public Task FireEventAsync<TPayload>(string methodName, IMessage<TPayload> message)
{
return SendEventAsync(methodName, message);
}
}
}

Просмотреть файл

@ -1,4 +1,7 @@
using System;
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.IO;
using Bond.IO.Safe;
using Bond.Protocols;
@ -28,6 +31,11 @@ namespace Bond.Comm.Tcp
/// </summary>
DeliverResponseToProxy,
/// <summary>
/// The frame was a valid Event.
/// </summary>
DeliverEventToService,
/// <summary>
/// The frame was not valid, and the caller should send an error to the remote host.
/// </summary>
@ -396,17 +404,12 @@ namespace Bond.Comm.Tcp
{
case PayloadType.Request:
case PayloadType.Response:
return ClassifyState.ValidFrame;
case PayloadType.Event:
Log.Warning("{0}.{1}: Received unimplemented payload type {2}.",
nameof(TcpProtocol), nameof(TransitionFrameComplete), headers.payload_type);
errorCode = ProtocolErrorCode.NOT_SUPPORTED;
return ClassifyState.MalformedFrame;
return ClassifyState.ValidFrame;
default:
Log.Warning("{0}.{1}: Received unrecognized payload type {2}.",
nameof(TcpProtocol), nameof(TransitionFrameComplete), headers.payload_type);
errorCode = ProtocolErrorCode.NOT_SUPPORTED;
return ClassifyState.MalformedFrame;
}
}
@ -429,6 +432,10 @@ namespace Bond.Comm.Tcp
disposition = FrameDisposition.DeliverResponseToProxy;
return ClassifyState.ClassifiedValidFrame;
case PayloadType.Event:
disposition = FrameDisposition.DeliverEventToService;
return ClassifyState.ClassifiedValidFrame;
default:
return ClassifyState.InternalStateError;
}

Просмотреть файл

@ -1,4 +1,7 @@
using System;
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
@ -45,6 +48,13 @@ namespace UnitTest.Tcp
payload_type = PayloadType.Event,
request_id = GoodRequestId
};
private static readonly TcpHeaders unknownTypeHeaders = new TcpHeaders
{
error_code = 0,
method_name = GoodMethod,
payload_type = (PayloadType)(-100),
request_id = GoodRequestId
};
private static Frame goodRequestFrame;
private static Frame goodResponseFrame;
@ -411,7 +421,7 @@ namespace UnitTest.Tcp
ProtocolErrorCode? errorCode = null;
var after = TcpProtocol.TransitionFrameComplete(
TcpProtocol.ClassifyState.FrameComplete, goodEventHeaders, ref errorCode);
TcpProtocol.ClassifyState.FrameComplete, unknownTypeHeaders, ref errorCode);
Assert.AreEqual(TcpProtocol.ClassifyState.MalformedFrame, after);
Assert.AreEqual(ProtocolErrorCode.NOT_SUPPORTED, errorCode);
}
@ -455,11 +465,6 @@ namespace UnitTest.Tcp
var after = TcpProtocol.TransitionValidFrame(TcpProtocol.ClassifyState.ValidFrame, null, ref disposition);
Assert.AreEqual(TcpProtocol.ClassifyState.InternalStateError, after);
Assert.AreEqual(TcpProtocol.FrameDisposition.Indeterminate, disposition);
after = TcpProtocol.TransitionValidFrame(
TcpProtocol.ClassifyState.ValidFrame, goodEventHeaders, ref disposition);
Assert.AreEqual(TcpProtocol.ClassifyState.InternalStateError, after);
Assert.AreEqual(TcpProtocol.FrameDisposition.Indeterminate, disposition);
}
[Test]
@ -538,6 +543,14 @@ namespace UnitTest.Tcp
Assert.Null(protocolErrorResult.Headers);
Assert.Null(protocolErrorResult.Payload.Array);
Assert.AreEqual(meaninglessErrorCode, protocolErrorResult.Error.error_code);
var eventResult = TcpProtocol.Classify(goodEventFrame);
Assert.AreEqual(TcpProtocol.FrameDisposition.DeliverEventToService, eventResult.Disposition);
Assert.AreEqual(goodEventHeaders.error_code, eventResult.Headers.error_code);
Assert.AreEqual(goodEventHeaders.method_name, eventResult.Headers.method_name);
Assert.AreEqual(goodEventHeaders.payload_type, eventResult.Headers.payload_type);
Assert.AreEqual(goodEventHeaders.request_id, eventResult.Headers.request_id);
Assert.AreEqual(goodEventFrame.Framelets[goodEventFrame.Count - 1].Contents, eventResult.Payload);
}
[Test]
@ -553,12 +566,6 @@ namespace UnitTest.Tcp
[Test]
public void Classify_MalformedFrame()
{
var eventResult = TcpProtocol.Classify(goodEventFrame);
Assert.AreEqual(TcpProtocol.FrameDisposition.SendProtocolError, eventResult.Disposition);
Assert.Null(eventResult.Headers);
Assert.Null(eventResult.Payload.Array);
Assert.AreEqual(ProtocolErrorCode.NOT_SUPPORTED, eventResult.ErrorCode);
var emptyResult = TcpProtocol.Classify(emptyFrame);
Assert.AreEqual(TcpProtocol.FrameDisposition.SendProtocolError, emptyResult.Disposition);
Assert.Null(emptyResult.Headers);

Просмотреть файл

@ -160,6 +160,17 @@ namespace UnitTest.Tcp
await testClientServer.Transport.StopAsync();
}
[Test]
public void TestServiceMethodTypeValidation_Throws()
{
var exception = Assert.Throws<ArgumentException>(async () => await SetupTestClientServer<TestServiceEventMismatch>());
Assert.That(exception.Message, Is.StringContaining("registered as Event but callback not implemented as such"));
exception = Assert.Throws<ArgumentException>(async () => await SetupTestClientServer<TestServiceReqResMismatch>());
Assert.That(exception.Message, Is.StringContaining("registered as RequestResponse but callback not implemented as such"));
exception = Assert.Throws<ArgumentException>(async () => await SetupTestClientServer<TestServiceUnsupported>());
Assert.That(exception.Message, Is.StringContaining("registered as invalid type"));
}
private class TestClientServer<TService>
{
public TService Service;
@ -274,5 +285,75 @@ namespace UnitTest.Tcp
return Task.FromResult<IMessage<Dummy>>(Message.FromPayload(result));
}
}
private class TestServiceEventMismatch : IService
{
public IEnumerable<ServiceMethodInfo> Methods
{
get
{
return new[]
{
new ServiceMethodInfo
{
MethodName = "TestService.RespondWithEmpty",
Callback = RespondWithEmpty,
CallbackType = ServiceCallbackType.Event
},
};
}
}
private Task<IMessage> RespondWithEmpty(IMessage request, ReceiveContext context, CancellationToken ct)
{
var emptyMessage = Message.FromPayload(new Bond.Void());
return Task.FromResult<IMessage>(emptyMessage);
}
}
private class TestServiceReqResMismatch : IService
{
public IEnumerable<ServiceMethodInfo> Methods
{
get
{
return new[]
{
new ServiceMethodInfo
{
MethodName = "TestService.DoBeep",
Callback = DoBeep,
CallbackType = ServiceCallbackType.RequestResponse
},
};
}
}
private Task DoBeep(IMessage request, ReceiveContext context, CancellationToken ct)
{
return CodegenHelpers.CompletedTask;
}
}
private class TestServiceUnsupported : IService
{
public IEnumerable<ServiceMethodInfo> Methods
{
get
{
return new[]
{
new ServiceMethodInfo
{
MethodName = "TestService.DoBeep",
Callback = DoBeep,
CallbackType = (ServiceCallbackType)(-100)
},
};
}
}
private Task DoBeep(IMessage request, ReceiveContext context, CancellationToken ct)
{
return CodegenHelpers.CompletedTask;
}
}
}
}

Просмотреть файл

@ -0,0 +1,77 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Bond.Examples.NotifyEvent
{
using System;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Bond.Comm;
using Bond.Comm.Tcp;
using Bond.Examples.Logging;
public static class NotifyEvent
{
private static TcpConnection s_connection;
public static void Main()
{
var transport = SetupAsync().Result;
MakeRequestsAndPrint(5);
Console.WriteLine("Done with all requests.");
// TODO: Shutdown not yet implemented.
// transport.StopAsync().Wait();
Console.ReadLine();
}
private async static Task<TcpTransport> SetupAsync()
{
var handler = new ConsoleLogger();
Log.AddHandler(handler);
var transport = new TcpTransportBuilder()
.SetUnhandledExceptionHandler(Transport.ToErrorExceptionHandler)
.Construct();
var assignAPortEndPoint = new IPEndPoint(IPAddress.Loopback, 0);
var notifyService = new NotifyEventService();
TcpListener notifyListener = transport.MakeListener(assignAPortEndPoint);
notifyListener.AddService(notifyService);
await notifyListener.StartAsync();
s_connection = await transport.ConnectToAsync(notifyListener.ListenEndpoint, CancellationToken.None);
return transport;
}
private static void MakeRequestsAndPrint(int numRequests)
{
var notifyEventProxy = new NotifyEventProxy<TcpConnection>(s_connection);
var rnd = new Random();
foreach (var requestNum in Enumerable.Range(0, numRequests))
{
UInt16 delay = (UInt16)rnd.Next(2000);
DoNotify(notifyEventProxy, requestNum, "notify" + requestNum.ToString(), delay);
}
}
private static void DoNotify(NotifyEventProxy<TcpConnection> proxy, int requestNum, string payload, UInt16 delay)
{
var request = new PingRequest { Payload = payload, DelayMilliseconds = delay };
proxy.NotifyAsync(request);
Console.WriteLine($"P Event #{requestNum} Delay: {delay}");
}
}
}

Просмотреть файл

@ -0,0 +1,28 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Bond.Examples.NotifyEvent
{
using Comm;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class NotifyEventService : NotifyEventServiceBase
{
private const UInt16 MaxDelayMilliseconds = 2000;
public override void NotifyAsync(IMessage<PingRequest> param)
{
PingRequest request = param.Payload.Deserialize();
if (request.DelayMilliseconds > 0)
{
UInt16 delayMs = Math.Min(MaxDelayMilliseconds, request.DelayMilliseconds);
Thread.Sleep(delayMs);
}
Console.WriteLine("Notified server-side, payload: " + request.Payload + " delay: "+request.DelayMilliseconds);
}
}
}

Просмотреть файл

@ -0,0 +1,15 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Bond.Examples.NotifyEvent;
struct PingRequest
{
0: string Payload;
1: uint16 DelayMilliseconds;
}
service NotifyEvent
{
nothing Notify(PingRequest);
}

Просмотреть файл

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="..\..\..\..\cs\build\Bond.CSharp.props" />
<PropertyGroup>
<ProjectGuid>{12279366-F646-4FEC-8CAA-B62A8EC477BB}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>notifyevent</RootNamespace>
<AssemblyName>notifyevent</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<ItemGroup>
<Compile Include="NotifyEvent.cs" />
<Compile Include="NotifyEventService.cs" />
<BondCodegen Include="notify.bond" />
<!-- Resharper Workaround -->
<Compile Include="$(IntermediateOutputPath)\notify_types.cs" Condition="False" />
<!-- End Resharper Workaround -->
<!-- TODO: edit the .targets to automatically include the comm files -->
<Compile Include="$(IntermediateOutputPath)\notify_interfaces.cs" />
<Compile Include="$(IntermediateOutputPath)\notify_proxies.cs" />
<Compile Include="$(IntermediateOutputPath)\notify_services.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="Attributes">
<HintPath>$(BOND_BINARY_PATH)\net45\Bond.Attributes.dll</HintPath>
</Reference>
<Reference Include="Bond">
<HintPath>$(BOND_BINARY_PATH)\net45\Bond.dll</HintPath>
</Reference>
<Reference Include="Bond.IO">
<HintPath>$(BOND_BINARY_PATH)\net45\Bond.IO.dll</HintPath>
</Reference>
<Reference Include="Bond.Comm.Interfaces">
<HintPath>$(BOND_BINARY_PATH)\net45\Bond.Comm.Interfaces.dll</HintPath>
</Reference>
<Reference Include="Bond.Comm.Tcp">
<HintPath>$(BOND_BINARY_PATH)\net45\Bond.Comm.Tcp.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\logging\logging.csproj">
<Project>{5c8132a8-c4b1-45e0-bca6-379da23b86d3}</Project>
<Name>logging</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(BOND_PATH)\build\Bond.CSharp.targets" />
</Project>