Skip to content

Commit ea5d44a

Browse files
author
aden.chen
committed
Enhance crontab job execution with re-entry protection
1 parent ad96e08 commit ea5d44a

4 files changed

Lines changed: 68 additions & 16 deletions

File tree

src/Infrastructure/BotSharp.Abstraction/Crontab/ICrontabService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ public interface ICrontabService
44
{
55
Task<List<CrontabItem>> GetCrontable();
66
Task ScheduledTimeArrived(CrontabItem item);
7+
Task ExecuteTimeArrivedItemWithReentryProtection(CrontabItem item);
78
}

src/Infrastructure/BotSharp.Abstraction/Crontab/Models/CrontabItem.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ public class CrontabItem : ScheduleTaskArgs
3232
[JsonPropertyName("trigger_type")]
3333
public CronTabItemTriggerType TriggerType { get; set; } = CronTabItemTriggerType.BackgroundWatcher;
3434

35+
[JsonPropertyName("reentry_protection")]
36+
public bool ReentryProtection { get; set; } = true;
37+
3538
public override string ToString()
3639
{
3740
return $"{Title}: {Description} [AgentId: {AgentId}, UserId: {UserId}]";

src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabService.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ limitations under the License.
1515
******************************************************************************/
1616

1717
using BotSharp.Abstraction.Agents.Models;
18+
using BotSharp.Abstraction.Infrastructures;
1819
using BotSharp.Abstraction.Repositories;
1920
using BotSharp.Abstraction.Repositories.Filters;
2021
using BotSharp.Abstraction.Tasks;
@@ -127,4 +128,62 @@ await HookEmitter.Emit<ICrontabHook>(_services, async hook =>
127128
}
128129
}, item.AgentId);
129130
}
131+
132+
public async Task ExecuteTimeArrivedItemWithReentryProtection(CrontabItem item)
133+
{
134+
if (!item.ReentryProtection)
135+
{
136+
await ExecuteTimeArrivedItem(item);
137+
return;
138+
}
139+
140+
var lockKey = $"crontab:execution:{item.Title}";
141+
using var scope = _services.CreateScope();
142+
var locker = scope.ServiceProvider.GetRequiredService<IDistributedLocker>();
143+
var acquired = false;
144+
var lockAcquired = false;
145+
146+
try
147+
{
148+
acquired = await locker.LockAsync(lockKey, async () =>
149+
{
150+
lockAcquired = true;
151+
_logger.LogInformation("Crontab: {0}, Distributed lock acquired, beginning execution...", item.Title);
152+
await ExecuteTimeArrivedItem(item);
153+
}, timeout: 600);
154+
155+
if (!acquired)
156+
{
157+
_logger.LogWarning("Crontab: {0}, Failed to acquire distributed lock, task is still executing, skipping this occurrence to prevent re-entry.", item.Title);
158+
}
159+
}
160+
catch (Exception ex)
161+
{
162+
if (!lockAcquired)
163+
{
164+
_logger.LogWarning("Crontab: {0}, Redis exception occurred before acquiring lock: {1}, executing without lock protection (re-entry protection disabled).", item.Title, ex.Message);
165+
await ExecuteTimeArrivedItem(item);
166+
}
167+
else
168+
{
169+
_logger.LogWarning("Crontab: {0}, Redis exception occurred after lock acquired: {1}, task execution completed but lock release failed.", item.Title, ex.Message);
170+
}
171+
}
172+
}
173+
174+
private async Task<bool> ExecuteTimeArrivedItem(CrontabItem item)
175+
{
176+
try
177+
{
178+
_logger.LogInformation($"Start running crontab {item.Title}");
179+
await ScheduledTimeArrived(item);
180+
_logger.LogInformation($"Complete running crontab {item.Title}");
181+
return true;
182+
}
183+
catch (Exception ex)
184+
{
185+
_logger.LogError(ex, $"Error when running crontab {item.Title}");
186+
return false;
187+
}
188+
}
130189
}

src/Infrastructure/BotSharp.OpenAPI/Controllers/Crontab/CrontabController.cs

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public async Task<CrontabSchedulingResult> SchedulingCrontab()
6060
{
6161
if (item.CheckNextOccurrenceEveryOneMinute())
6262
{
63-
_logger.LogInformation("Crontab: {0}, One occurrence was matched, Beginning execution...", item.Title);
63+
_logger.LogInformation($"Crontab: {item.Title}, One occurrence was matched, attempting to execute...");
6464
Task.Run(() => ExecuteTimeArrivedItem(item, _services));
6565
result.OccurrenceMatchedItems.Add(item.Title);
6666
}
@@ -84,21 +84,10 @@ private async Task<List<CrontabItem>> GetCrontabItems(string? title = null)
8484
return allowedCrons.Where(cron => cron.Title.IsEqualTo(title)).ToList();
8585
}
8686

87-
private async Task<bool> ExecuteTimeArrivedItem(CrontabItem item, IServiceProvider services)
87+
private async Task ExecuteTimeArrivedItem(CrontabItem item, IServiceProvider services)
8888
{
89-
try
90-
{
91-
using var scope = services.CreateScope();
92-
var crontabService = scope.ServiceProvider.GetRequiredService<ICrontabService>();
93-
_logger.LogInformation($"Start running crontab {item.Title}");
94-
await crontabService.ScheduledTimeArrived(item);
95-
_logger.LogInformation($"Complete running crontab {item.Title}");
96-
return true;
97-
}
98-
catch (Exception ex)
99-
{
100-
_logger.LogError(ex, $"Error when running crontab {item.Title}");
101-
return false;
102-
}
89+
using var scope = services.CreateScope();
90+
var crontabService = scope.ServiceProvider.GetRequiredService<ICrontabService>();
91+
await crontabService.ExecuteTimeArrivedItemWithReentryProtection(item);
10392
}
10493
}

0 commit comments

Comments
 (0)