Найти в Дзене

Вложенные циклические вызовы сервисов в Apache.Ignite

Итак, речь идет о продукте Apache.Ignite (вариант .NET) и его части, называемой Service Grid. Если вкратце - вычислительный кластер, архитектура (микро)сервисов и все такое. И вот возник у меня на днях серьезный вопрос: что будет, если сервис А вызовет сервис Б, а тот в свою очередь вызовет другой метод сервиса А?

Вопрос не так уж и прост, если вспомнить, что вся эта структура сервисов является надстройкой над основной частью вычислительного кластера, работающего с акторами и потоками сообщений. И, кстати, тут есть лимит на вложенные вызовы - каждый вызов сервиса до тех пор, пока он не вернул управление обратно вызывающему коду, занимает на целевом узле (с определенными оговорками) один поток исполнения из общего пула. А объем пула потоков, он, знаете ли, по умолчанию не очень-то большой, да и не может быть большим. Это вам не стек вызовов.

Короче, было у меня опасение, что второй вызов сервиса А просто зависнет - если на каждый экземпляра сервиса Ignite тратит не более одного потока на исходном узле.

Пробный пример выглядит так: есть сервисы ServiceTest1 / IServiceTest1 и ServiceTest2 / IServiceTest2, размещенные на одном узле, ну и первый вызывает второй, а второй в ответ вызывает первый. Код привожу ниже.

IServiceTest1:

namespace IgniteSamples {
public interface IServiceTest1
{
void Run();
void Callback();
}
}

IServiceTest2:

namespace IgniteSamples {
public interface IServiceTest2
{
void Run();
}
}

Сами сервисы. ServiceTest1:

using System;
using Apache.Ignite.Core;
using Apache.Ignite.Core.Resource;
using Apache.Ignite.Core.Services;

namespace IgniteSamples {
[Serializable]
public class ServiceTest1: IService, IServiceTest1
{
[NonSerialized]
private long _flag;

// Auto-injected Ignite instance.
[InstanceResource]
private readonly IIgnite _ignite;

#region Implementation of IService
/// <inheritdoc />
public void Init(IServiceContext context)
{

}

/// <inheritdoc />
public void Execute(IServiceContext context)
{
_flag =
0;
}

/// <inheritdoc />
public void Cancel(IServiceContext context)
{

}

#endregion
#region Implementation of IServiceTest1
/// <inheritdoc />
public void Run()
{
IServiceTest2 srv = _ignite.GetServices().GetServiceProxy<IServiceTest2>("ServiceTest2");

srv.Run();

while (true)
{
lock(this)
if (_flag == 1)
break;
}
}

/// <inheritdoc />
public void Callback()
{
lock (this)
_flag =
1;
}

#endregion
}
}

ServiceTest2:

-2
using Apache.Ignite.Core;
using Apache.Ignite.Core.Resource;
using Apache.Ignite.Core.Services;

namespace IgniteSamples {
public class ServiceTest2: IService, IServiceTest2
{
// Auto-injected Ignite instance.
[InstanceResource]
private readonly IIgnite _ignite;

#region Implementation of IService
/// <inheritdoc />
public void Init(IServiceContext context)
{

}

/// <inheritdoc />
public void Execute(IServiceContext context)
{

}

/// <inheritdoc />
public void Cancel(IServiceContext context)
{

}

#endregion
#region Implementation of IServiceTest2
/// <inheritdoc />
public void Run()
{
IServiceTest1 srv = _ignite.GetServices().GetServiceProxy<IServiceTest1>("ServiceTest1");
srv.Callback();
}

#endregion
}
}

Ну и теперь код Main:

-3
using System;
using Apache.Ignite.Core;
using Apache.Ignite.Core.Services;

namespace IgniteSamples {
static class Program {
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
IIgnite ignite = Ignition.Start();

try {
IServices services = ignite.GetServices();
services.DeployClusterSingleton("ServiceTest1",
new ServiceTest1());
services.DeployClusterSingleton("ServiceTest2",
new ServiceTest2());

IServiceTest1 srv = ignite.GetServices().GetServiceProxy<IServiceTest1>("ServiceTest1");
srv.Run();
}
finally {
ignite.Dispose();
}
}
}
}

Ну и вывод одной строкой: к счастью, мои опасения не оправдались, вызовы проходят, даже один экземпляр сервиса работает в многопотоковом режиме.