Service Fabric Actor custom actor service SaveReminderAsync usage - azure-service-fabric

I have a custom actor service, and I want to be able to add reminders to my actors from here, and not to have to call the actor directly for this. I do not want to wait to get in line if the actor is busy, but have it fire a reminder when needed.
I am trying to use StateProvider.SaveReminderAsync and from what the function summary says it should work exactly like I want.. but it does not, and I can not find a single sample online about this function. I've been looking for 2 days and tried a bunch of things to get it working with no luck.
Thanks for any help
public class ActorReminderTestServices : ActorService, IActorReminderTestService, IService
{
public ActorReminderTestServices(StatefulServiceContext context,
ActorTypeInformation actorTypeInfo,
Func<ActorService, ActorId, ActorBase> actorFactory = null,
Func<ActorBase, IActorStateProvider, IActorStateManager> stateManagerFactory = null,
IActorStateProvider stateProvider = null,
ActorServiceSettings settings = null)
: base(context, actorTypeInfo, actorFactory, stateManagerFactory, stateProvider, settings)
{
}
protected override async Task RunAsync(CancellationToken cancellationToken)
{
await base.RunAsync(cancellationToken);
#region Code to limit this to running just on 1 partition
FabricClient _fabricClient = new FabricClient();
System.Fabric.Query.ServicePartitionList _partitionList = await _fabricClient
.QueryManager.GetPartitionListAsync(this.Context.ServiceName);
System.Fabric.Query.Partition _1stPartition = _partitionList.OrderBy(a =>
(a.PartitionInformation as Int64RangePartitionInformation).LowKey).FirstOrDefault();
if (this.Partition.PartitionInfo.Id != _1stPartition.PartitionInformation.Id)
{
return;
}
#endregion
Task.Run(async () =>
{
await Task.Delay(10000);
await this.CreateActors(cancellationToken);
});
}
public async Task CreateActors(CancellationToken cancellationToken)
{
try
{
Guid _test2 = Guid.Parse("4C1DC22F-27DF-40C0-AD38-DC1971BDB281"); // {4C1DC22F-27DF-40C0-AD38-DC1971BDB281}
ActorId _actor2 = new ActorId(_test2);
// this function on the stateprovider.. how is this suppose to be used??
// from looking at the SF source this object that is used in the actual actor
// in an internal class, I tried creating my own version, but than the issues is it uses IActorManager
// and that is internal.. how can I use this function. I would like to loop through
// all the actors and add a reminder to all of them with out having to call the actor directly
await this.StateProvider.SaveReminderAsync(_actor2, new IActorReminder { Name = "testing", DueTime = TimeSpan.FromSeconds(1), Period = TimeSpan.FromSeconds(1), State = null }, cancellationToken);
}
catch (Exception ex)
{
}
}
}
}

Related

The configured execution strategy 'RetryTransactionExecutionStrategy' does not support user initiated transactions

We wrote our own simple execution strategy to retry saving any data using our DbContext when it runs into a table lock timeout.
public class RetryTransactionExecutionStrategy : DbExecutionStrategy
{
public RetryTransactionExecutionStrategy() : base()
{
}
protected override bool ShouldRetryOn(Exception exception)
{
while (exception != null)
{
if (exception is MySqlException ex
&& ex.Number == 1205) // Deadlock error code
{
return true;
}
exception = exception.InnerException;
}
return false;
}
}
We register it by using the DbConfig class, in the same folder as the context class.
public class DbConfig : DbConfiguration
{
public DbConfig()
{
SetExecutionStrategy(MySqlProviderInvariantName.ProviderName, () => new RetryTransactionExecutionStrategy());
}
}
Now most regular usage of the context will use the retry execution strategy. However, transactions are a more special case. Microsoft mentions usage of them in their documentation, and tells the user to manually call the execution strategy, like this:
var executionStrategy = new RetryTransactionExecutionStrategy();
executionStrategy.Execute(() =>
{
using (PigDbAccountEntities pigDbAccountEntities = new PigDbAccountEntities())
{
using (var dbtransaction = pigDbAccountEntities.Database.BeginTransaction())
{
try
{
//work on some data
pigDbAccountEntities.SaveChanges();
//work on some more data
pigDbAccountEntities.SaveChanges();
//work on even more data
pigDbAccountEntities.SaveChanges();
dbtransaction.Commit();
isSaved = true;
}
catch (Exception ex)
{
dbtransaction.Rollback();
Logger.Instance.Log(LogLevel.ERROR, LogSource.DB, "error in AccountEntityManager.SaveApplicationUser", ex);
}
}
}
});
And yet we still get this error message:
The configured execution strategy 'RetryTransactionExecutionStrategy' does not support user initiated transactions. See http://go.microsoft.com/fwlink/?LinkId=309381 for additional information.
Any idea on what to do/check?

How to handle job recovering in Quartz.Net

I use Quartz 3.0.7 in my application that use .NET Core 2.2 platform.
I use ms sql server for Quartz action tracking. Quartz tracks and stores its actions in database and it's fine.
Configuration of my StdSchedulerFactory:
["quartz.scheduler.instanceName"] = "StdScheduler",
["quartz.scheduler.instanceId"] = $"{Environment.MachineName}-{Guid.NewGuid()}",
["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz",
["quartz.jobStore.useProperties"] = "true",
["quartz.jobStore.dataSource"] = "default",
["quartz.jobStore.tablePrefix"] = "QRTZ_",
// if running MS SQL Server we need this
["quartz.jobStore.lockHandler.type"] = "Quartz.Impl.AdoJobStore.UpdateLockRowSemaphore, Quartz",
["quartz.dataSource.default.connectionString"] = #"Server=DESKTOP-D64SJFJ\MSSQLSERVER14;Database=quartz;Trusted_Connection=True;",
["quartz.dataSource.default.provider"] = "SqlServer",
[$"{StdSchedulerFactory.PropertyObjectSerializer}.type"] = "json",
[StdSchedulerFactory.PropertySchedulerInterruptJobsOnShutdownWithWait] = "true",
I want to recover each interrupted Job. How should I organize logic of my IHostedService for supporting Job Recovering?
When I Shutdown my application during my job is running then When I start my application again interrupted job doesn't run.
My IHostedService code:
public class QuartzHostedService : IHostedService
{
private readonly ISchedulerFactory _schedulerFactory;
private readonly IJobFactory _jobFactory;
private readonly IEnumerable<JobSchedule> _jobSchedules;
public QuartzHostedService(
ISchedulerFactory schedulerFactory,
IJobFactory jobFactory,
IEnumerable<JobSchedule> jobSchedules)
{
_schedulerFactory = schedulerFactory;
_jobSchedules = jobSchedules;
_jobFactory = jobFactory;
}
public IScheduler Scheduler { get; set; }
public async Task StartAsync(CancellationToken cancellationToken)
{
Scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
Scheduler.JobFactory = _jobFactory;
await Scheduler.Start(cancellationToken);
foreach (var jobSchedule in _jobSchedules)
{
var job = CreateJob(jobSchedule);
var trigger = CreateTrigger(jobSchedule);
if (!await Scheduler.CheckExists(job.Key, cancellationToken))
{
// if the job doesn't already exist, we can create it, along with its trigger. this prevents us
// from creating multiple instances of the same job when running in a clustered environment
await Scheduler.ScheduleJob(job, trigger);
}
else
{
// if the job has exactly one trigger, we can just reschedule it, which allows us to update the schedule for
// that trigger.
var triggers = await Scheduler.GetTriggersOfJob(job.Key);
if (triggers.Count == 1)
{
await Scheduler.RescheduleJob(triggers.First().Key, trigger);
}
else
{
// if for some reason the job has multiple triggers, it's easiest to just delete and re-create the job,
// since we want to enforce a one-to-one relationship between jobs and triggers
await Scheduler.DeleteJob(job.Key);
await Scheduler.ScheduleJob(job, trigger);
}
}
}
}
public async Task StopAsync(CancellationToken cancellationToken)
{
await Scheduler?.Shutdown(cancellationToken);
}
private static IJobDetail CreateJob(JobSchedule schedule)
{
var jobType = schedule.JobType;
return JobBuilder
.Create(jobType)
.WithIdentity(jobType.FullName)
.WithDescription(jobType.Name)
.RequestRecovery(true)
.StoreDurably()
.Build();
}
private static ITrigger CreateTrigger(JobSchedule schedule)
{
return TriggerBuilder
.Create()
.WithIdentity($"{schedule.JobType.FullName}.trigger")
.WithCronSchedule(schedule.CronExpression)
.WithDescription(schedule.CronExpression)
.Build();
}
}
My startup.cs:
private void ConfigureQuartz(IServiceCollection services)
{
services.AddHostedService<QuartzHostedService>();
services.AddSingleton<IJobFactory, SingletonJobFactory>();
services.AddSingleton<ISchedulerFactory>(new StdSchedulerFactory(StdSchedulerFactoryConfiguration()));
services.AddSingleton<AuthKeyExpiresJob>();
//services.AddSingleton<AuthKeyWillExpireJob>();
services.AddSingleton(new JobSchedule(
typeof(AuthKeyExpiresJob),
"0 14 11 ? * *"));
}

Revert DbContext.Savechanges in case a second DbContext.Savechanges fail

I have the following code, which stores information in two different tables in the same method
public static async Task<Response> AddStockTransaction(StockTransactionsHeader header, List<StockTransactionsDetails> details)
{
using (DataContext dbContext = new DataContext())
{
try
{
dbContext.StockTransactionsHeader.Add(header);
await dbContext.SaveChangesAsync();
int hearderID = header.TransactionHeaderID;
foreach (var item in details)
{
item.TransactionHeaderID = hearderID;
}
dbContext.StockTransactionsDetails.AddRange(details);
await dbContext.SaveChangesAsync();
return new Response
{
IsSuccess = true
};
}
catch (Exception ex)
{
return new Response
{
IsSuccess = false,
Message = ex.Message
};
}
}
}
How can I do, in case there is an exception in the second SaveChanges () to revert the first one?
Once SaveChanges has been called, your datat is stored on your database. You should not call SaveChanges more than once in a call, unless you are willingly to persist the intermediate steps.
You can use a transaction scope to create managed transactions :
using (TransactionScope scope = CreateTransactionScope())
{
DoSomthing(context);
scope.Complete();
}
however, if the failure of the second part involves rolling back the first one, this means that both parts belong to the same transaction, therefore simply omitting the first SaveChanges would turn your code into a single transaction.
From my another awnser: You could use DbTransaction class.
private void TestTransaction()
{
var context = new MyContext(connectionString);
using (var transaction = context.Database.BeginTransaction())
{
try
{
// do your stuff
// commit changes
transaction.Commit();
}
catch
{
// 'undo' all changes
transaction.Rollback();
}
}
}

Is it possible to instruct ServicePartitionClient to talk to a specific node in service fabric?

I have
public class HttpCommunicationClient : HttpClient, ICommunicationClient
{
public HttpCommunicationClient()
: base(new HttpClientHandler() { AllowAutoRedirect = false, UseCookies = false })
{
}
public HttpCommunicationClient(HttpMessageHandler handler)
: base(handler)
{
}
public HttpCommunicationClient(HttpMessageHandler handler, bool disposeHandler)
: base(handler, disposeHandler)
{
}
#region ICommunicationClient
string ICommunicationClient.ListenerName { get; set; }
ResolvedServiceEndpoint ICommunicationClient.Endpoint { get; set; }
ResolvedServicePartition ICommunicationClient.ResolvedServicePartition { get; set; }
#endregion ICommunicationClient
}
and
public class HttpCommunicationClientFactory : CommunicationClientFactoryBase<HttpCommunicationClient>
{
private readonly Func<HttpCommunicationClient> _innerDispatcherProvider;
public HttpCommunicationClientFactory(IServicePartitionResolver servicePartitionResolver = null, IEnumerable<IExceptionHandler> exceptionHandlers = null, string traceId = null)
: this(() => new HttpCommunicationClient(), servicePartitionResolver, exceptionHandlers, traceId)
{
}
public HttpCommunicationClientFactory(Func<HttpCommunicationClient> innerDispatcherProvider, IServicePartitionResolver servicePartitionResolver = null, IEnumerable<IExceptionHandler> exceptionHandlers = null, string traceId = null)
: base(servicePartitionResolver, exceptionHandlers, traceId)
{
if (innerDispatcherProvider == null)
{
throw new ArgumentNullException(nameof(innerDispatcherProvider));
}
_innerDispatcherProvider = innerDispatcherProvider;
}
protected override void AbortClient(HttpCommunicationClient dispatcher)
{
if (dispatcher != null)
{
dispatcher.Dispose();
}
}
protected override Task<HttpCommunicationClient> CreateClientAsync(string endpoint, CancellationToken cancellationToken)
{
var dispatcher = _innerDispatcherProvider.Invoke();
dispatcher.BaseAddress = new Uri(endpoint, UriKind.Absolute);
return Task.FromResult(dispatcher);
}
protected override bool ValidateClient(HttpCommunicationClient dispatcher)
{
return dispatcher != null && dispatcher.BaseAddress != null;
}
protected override bool ValidateClient(string endpoint, HttpCommunicationClient dispatcher)
{
return dispatcher != null && dispatcher.BaseAddress == new Uri(endpoint, UriKind.Absolute);
}
}
and is using it like below
var servicePartitionClient = new ServicePartitionClient<HttpCommunicationClient>(_httpClientFactory,
_options.ServiceUri,
_options.GetServicePartitionKey?.Invoke(context),
_options.TargetReplicaSelector,
_options.ListenerName,
_options.OperationRetrySettings);
using (var responseMessage = await servicePartitionClient.InvokeWithRetryAsync(httpClient => ExecuteServiceCallAsync(httpClient, context)))
{
await responseMessage.CopyToCurrentContext(context);
}
The question is now, if I know at the time of using ServicePartitionClient that I would like it to connect to a specific node, is there any way to do so?
The case is that its a gateway application that forward requests to other services and I would like it to behave like with sticky sessions.
It makes more sense to think in terms of services than nodes. So rather than connecting to a specific node, you're actually connecting to a specific instance of a service.
When you're connecting to a service, if it's stateless, it shouldn't matter which instance you connect to, by definition of it being stateless. If you find that a user is tied to a specific instance of a service, that service is stateful (it's keeping track of some user state), and that's exactly the type of scenario that stateful services are meant to handle.
I found a solution, where I in the ExecuteServiceCallAsync call below reads a cookie from request with the information about which node it was connected to if its a sticky session, and if no cookie is present i set one with the information from the request. If the node dont exist any more the cookie is updated to new node.
using (var responseMessage = await servicePartitionClient.InvokeWithRetryAsync(httpClient => ExecuteServiceCallAsync(httpClient, context)))
{
await responseMessage.CopyToCurrentContext(context);
}

Unstable application uses SqlDependency. Several states and errors

I have a windows application using SqlDependency running at separated thread pool, this application represents a log monitor UI get the latest rows added in a specific table in the database and view it in a DataGridView. You can see the application source code from this LINK, or follow this script.
const string tableName = "OutgoingLog";
const string statusMessage = "{0} changes have occurred.";
int changeCount = 0;
private static DataSet dataToWatch = null;
private static SqlConnection connection = null;
private static SqlCommand command = null;
public frmMain()
{
InitializeComponent();
}
private bool CanRequestNotifications()
{
// In order to use the callback feature of the
// SqlDependency, the application must have
// the SqlClientPermission permission.
try
{
SqlClientPermission perm = new SqlClientPermission(PermissionState.Unrestricted);
perm.Demand();
return true;
}
catch
{
return false;
}
}
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
// This event will occur on a thread pool thread.
// Updating the UI from a worker thread is not permitted.
// The following code checks to see if it is safe to
// update the UI.
ISynchronizeInvoke i = (ISynchronizeInvoke)this;
// If InvokeRequired returns True, the code
// is executing on a worker thread.
if (i.InvokeRequired)
{
// Create a delegate to perform the thread switch.
OnChangeEventHandler tempDelegate = new OnChangeEventHandler(dependency_OnChange);
object[] args = { sender, e };
// Marshal the data from the worker thread
// to the UI thread.
i.BeginInvoke(tempDelegate, args);
return;
}
// Remove the handler, since it is only good
// for a single notification.
SqlDependency dependency = (SqlDependency)sender;
dependency.OnChange -= dependency_OnChange;
// At this point, the code is executing on the
// UI thread, so it is safe to update the UI.
++changeCount;
lblChanges.Text = String.Format(statusMessage, changeCount);
// Reload the dataset that is bound to the grid.
GetData();
}
AutoResetEvent running = new AutoResetEvent(true);
private void GetData()
{
// Start the retrieval of data on another thread to let the UI thread free
ThreadPool.QueueUserWorkItem(o =>
{
running.WaitOne();
// Empty the dataset so that there is only
// one batch of data displayed.
dataToWatch.Clear();
// Make sure the command object does not already have
// a notification object associated with it.
command.Notification = null;
// Create and bind the SqlDependency object
// to the command object.
SqlDependency dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
using (SqlDataAdapter adapter = new SqlDataAdapter(command))
{
adapter.Fill(dataToWatch, tableName);
try
{
running.Set();
}
finally
{
// Update the UI
dgv.Invoke(new Action(() =>
{
dgv.DataSource = dataToWatch;
dgv.DataMember = tableName;
//dgv.FirstDisplayedScrollingRowIndex = dgv.Rows.Count - 1;
}));
}
}
});
}
private void btnAction_Click(object sender, EventArgs e)
{
changeCount = 0;
lblChanges.Text = String.Format(statusMessage, changeCount);
// Remove any existing dependency connection, then create a new one.
SqlDependency.Stop("Server=.; Database=SMS_Tank_Log;UID=sa;PWD=hana;MultipleActiveResultSets=True");
SqlDependency.Start("Server=.; Database=SMS_Tank_Log;UID=sa;PWD=hana;MultipleActiveResultSets=True");
if (connection == null)
{
connection = new SqlConnection("Server=.; Database=SMS_Tank_Log;UID=sa;PWD=hana;MultipleActiveResultSets=True");
}
if (command == null)
{
command = new SqlCommand("select * from OutgoingLog", connection);
//SqlParameter prm =
// new SqlParameter("#Quantity", SqlDbType.Int);
//prm.Direction = ParameterDirection.Input;
//prm.DbType = DbType.Int32;
//prm.Value = 100;
//command.Parameters.Add(prm);
}
if (dataToWatch == null)
{
dataToWatch = new DataSet();
}
GetData();
}
private void frmMain_Load(object sender, EventArgs e)
{
btnAction.Enabled = CanRequestNotifications();
}
private void frmMain_FormClosing(object sender, FormClosingEventArgs e)
{
SqlDependency.Stop("Server=.; Database=SMS_Tank_Log;UID=sa;PWD=hana;MultipleActiveResultSets=True");
}
The problem:
I have many situations of errors, (images in the first comment)
(No. 1):
I got this error dialog, and I don't know its reason.
(No. 2):
I got nothing in my grid view (No errors, and no data).
(No. 3):
I got only columns names and no rows, although the table has rows.
I need help please.
I may be wrong but a DataSet does not seem to have notification capability so the DataGridView may be surprised if you change it behind its back.
You could try to explicitly show your're changing the data source by first setting it to null:
dgv.DataSource = null;
dgv.DataSource = dataToWatch;
dgv.DataMember = tableName;
It's worth a try...