Skip to content

Commit 2a5b3db

Browse files
committed
Proper error handling for TaskScheduler and several other fixes & enhancements
1 parent 2f78cf0 commit 2a5b3db

11 files changed

Lines changed: 90 additions & 28 deletions

File tree

changelog.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44

55
### New Features
66
* #141 Payment and shipping methods by customer roles
7-
* #67 Restrict payment methods to certain countries
8-
* #94 Restrict payment methods to certain shipping methods
9-
* #526 Min\max amount option for which the payment method should be offered during checkout
7+
* #67 Restrict payment methods to countries
8+
* #94 Restrict payment methods to shipping methods
9+
* #584 Email attachment support for message templates
10+
* Attach order invoice PDF automatically to order notification emails
11+
* #526 Min/Max amount option for which the payment method should be offered during checkout
1012
* #718 ShopConnector: Import option for "Published" and "Disable buy\wishlist button"
1113
* #702 Facebook and Twitter external authentication suitable for multi-stores
12-
* New scheduled task: clear e-mail queue
13-
* New scheduled task: clear uploadeded transient media files
14+
* New scheduled task: Clear e-mail queue
15+
* New scheduled task: Clear uploadeded transient media files
1416
* #704 Make primary store currency suitable for multi-stores
1517
* #727 Web-API: Option to deactivate TimestampOlderThanLastRequest validation
1618
* #731 Web-API: Allow deletion and inserting of product category and manufacturer assignments
@@ -23,7 +25,9 @@
2325
* (Perf) Implemented static caches for URL aliases and localized properties. Increases app startup and request speed by up to 30%.
2426
* (Perf) Significantly reduced number of database reads during product list rendering. Increases request speed by up to 10%.
2527
* (Perf) Implemented 2nd level cache for infrequently changed entities. Increases request speed by up to 10%.
26-
* #721 Message Queue: implemented "Delete all"
28+
* TaskScheduler: Rewritten from scratch to be suitable for Web Farms
29+
* TaskScheduler: Editing tasks does not require app restart anymore
30+
* TaskScheduler: Enhanced UI
2731
* #721 Message Queue: implemented "Delete all"
2832
* #725 Prevent LowestProductPrice being 0
2933
* #709 News feed produced invalid RSS feed. Added content:encoded. Added maximum news age setting for feed export.

src/Libraries/SmartStore.Core/Logging/TraceLogger.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public void InsertLog(LogContext context)
8989
var msg = context.ShortMessage;
9090
if (context.FullMessage.HasValue())
9191
{
92-
msg += "\r\n{0}".FormatCurrent(context.FullMessage);
92+
msg += "{0}{1}".FormatCurrent(Environment.NewLine, context.FullMessage);
9393
}
9494
_traceSource.TraceEvent(type, (int)type, "{0}: {1}".FormatCurrent(type.ToString().ToUpper(), msg));
9595
}

src/Libraries/SmartStore.Services/Messages/QueuedMessagesSendTask.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public QueuedMessagesSendTask(IQueuedEmailService queuedEmailService)
1616

1717
public void Execute(TaskExecutionContext ctx)
1818
{
19-
const int pageSize = 100;
19+
const int pageSize = 1000;
2020
const int maxTries = 3;
2121

2222
for (int i = 0; i < 9999999; ++i)
@@ -26,7 +26,8 @@ public void Execute(TaskExecutionContext ctx)
2626
MaxSendTries = maxTries,
2727
PageIndex = i,
2828
PageSize = pageSize,
29-
Expand = "Attachments"
29+
Expand = "Attachments",
30+
UnsentOnly = true
3031
};
3132
var queuedEmails = _queuedEmailService.SearchEmails(q);
3233

src/Libraries/SmartStore.Services/Tasks/ChangeTaskSchedulerBaseUrlConsumer.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using SmartStore.Core.Domain.Stores;
88
using SmartStore.Core.Events;
99
using SmartStore.Services.Stores;
10+
using SmartStore.Utilities;
1011

1112
namespace SmartStore.Services.Tasks
1213
{
@@ -18,27 +19,37 @@ public class ChangeTaskSchedulerBaseUrlConsumer :
1819
private readonly ITaskScheduler _taskScheduler;
1920
private readonly IStoreService _storeService;
2021
private readonly HttpContextBase _httpContext;
22+
private readonly bool _shouldChange;
2123

2224
public ChangeTaskSchedulerBaseUrlConsumer(ITaskScheduler taskScheduler, IStoreService storeService, HttpContextBase httpContext)
2325
{
2426
this._taskScheduler = taskScheduler;
2527
this._storeService = storeService;
2628
this._httpContext = httpContext;
29+
this._shouldChange = CommonHelper.GetAppSetting<string>("sm:TaskSchedulerBaseUrl").IsWebUrl() == false;
2730
}
2831

2932
public void HandleEvent(EntityInserted<Store> eventMessage)
3033
{
31-
_taskScheduler.SetBaseUrl(_storeService, _httpContext);
34+
HandleEventCore();
3235
}
3336

3437
public void HandleEvent(EntityUpdated<Store> eventMessage)
3538
{
36-
_taskScheduler.SetBaseUrl(_storeService, _httpContext);
39+
HandleEventCore();
3740
}
3841

3942
public void HandleEvent(EntityDeleted<Store> eventMessage)
4043
{
41-
_taskScheduler.SetBaseUrl(_storeService, _httpContext);
44+
HandleEventCore();
4245
}
46+
47+
private void HandleEventCore()
48+
{
49+
if (_shouldChange)
50+
{
51+
_taskScheduler.SetBaseUrl(_storeService, _httpContext);
52+
}
53+
}
4354
}
4455
}

src/Libraries/SmartStore.Services/Tasks/ITaskScheduler.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,21 +74,20 @@ public static class ITaskSchedulerExtensions
7474

7575
internal static void SetBaseUrl(this ITaskScheduler scheduler, IStoreService storeService, HttpContextBase httpContext)
7676
{
77-
var path = VirtualPathUtility.ToAbsolute("~/TaskScheduler");
7877
string url = "";
7978

8079
if (!httpContext.Request.IsLocal)
8180
{
8281
var defaultStore = storeService.GetAllStores().FirstOrDefault(x => storeService.IsStoreDataValid(x));
8382
if (defaultStore != null)
8483
{
85-
url = defaultStore.Url;
84+
url = defaultStore.Url.EnsureEndsWith("/") + "TaskScheduler";
8685
}
8786
}
8887

8988
if (url.IsEmpty())
9089
{
91-
url = WebHelper.GetAbsoluteUrl(path, httpContext.Request);
90+
url = WebHelper.GetAbsoluteUrl(VirtualPathUtility.ToAbsolute("~/TaskScheduler"), httpContext.Request);
9291
}
9392

9493
scheduler.BaseUrl = url;

src/Libraries/SmartStore.Services/Tasks/InitializeSchedulerFilter.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Web.Mvc;
1111
using SmartStore.Core.Data;
1212
using SmartStore.Core.Logging;
13+
using SmartStore.Utilities;
1314

1415
namespace SmartStore.Services.Tasks
1516
{
@@ -38,7 +39,20 @@ public void OnAuthorization(AuthorizationContext filterContext)
3839
var tasks = taskService.GetAllTasks(true);
3940
taskService.CalculateNextRunTimes(tasks, true /* isAppStart */);
4041

41-
taskScheduler.SetBaseUrl(storeService, filterContext.HttpContext);
42+
var baseUrl = CommonHelper.GetAppSetting<string>("sm:TaskSchedulerBaseUrl");
43+
if (baseUrl.IsWebUrl())
44+
{
45+
taskScheduler.BaseUrl = baseUrl;
46+
}
47+
else
48+
{
49+
// autoresolve base url
50+
taskScheduler.SetBaseUrl(storeService, filterContext.HttpContext);
51+
}
52+
53+
var sweepInterval = CommonHelper.GetAppSetting<int>("sm:TaskSchedulerSweepInterval", 60);
54+
taskScheduler.SweepInterval = TimeSpan.FromSeconds(sweepInterval);
55+
4256
taskScheduler.Start();
4357

4458
logger.Information("Initialized TaskScheduler with base url '{0}'".FormatInvariant(taskScheduler.BaseUrl));

src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public class DefaultTaskScheduler : DisposableObject, ITaskScheduler, IRegistere
1919
private string _baseUrl;
2020
private System.Timers.Timer _timer;
2121
private bool _shuttingDown;
22+
private int _errCount;
2223
private readonly ConcurrentDictionary<string, bool> _authTokens = new ConcurrentDictionary<string, bool>();
2324

2425
public DefaultTaskScheduler()
@@ -115,7 +116,7 @@ protected internal virtual void CallEndpoint(string url)
115116
req.UserAgent = "SmartStore.NET";
116117
req.Method = "POST";
117118
req.ContentType = "text/plain";
118-
req.ContentLength = 0;
119+
req.ContentLength = 0;
119120

120121
string authToken = Guid.NewGuid().ToString();
121122
_authTokens.TryAdd(authToken, true);
@@ -125,22 +126,48 @@ protected internal virtual void CallEndpoint(string url)
125126
{
126127
if (t.IsFaulted)
127128
{
128-
// TODO: Now what?! Disable timer?
129-
using (var logger = new TraceLogger())
129+
HandleException(t.Exception, url);
130+
_errCount++;
131+
if (_errCount >= 10)
130132
{
131-
foreach (var ex in t.Exception.InnerExceptions)
133+
// 10 failed attempts in succession. Stop the timer!
134+
this.Stop();
135+
using (var logger = new TraceLogger())
132136
{
133-
logger.Error("Error while calling TaskScheduler endpoint (URL: {0})".FormatInvariant(url), ex);
137+
logger.Information("Stopping TaskScheduler sweep timer. Too many failed requests in succession.");
134138
}
135139
}
136140
}
137141
else
138142
{
143+
_errCount = 0;
139144
t.Result.Dispose();
140145
}
141146
});
142147
}
143148

149+
private void HandleException(AggregateException exception, string url)
150+
{
151+
using (var logger = new TraceLogger())
152+
{
153+
string msg = "Error while calling TaskScheduler endpoint '{0}'.".FormatInvariant(url);
154+
var wex = exception.InnerExceptions.OfType<WebException>().FirstOrDefault();
155+
156+
if (wex == null)
157+
{
158+
logger.Error(msg, exception);
159+
}
160+
else
161+
{
162+
using (var response = wex.Response as HttpWebResponse)
163+
{
164+
msg += " HTTP {0}, {1}".FormatCurrent((int)response.StatusCode, response.StatusDescription);
165+
logger.Error(msg);
166+
}
167+
}
168+
}
169+
}
170+
144171
private void CheckUrl(string url)
145172
{
146173
if (!url.IsWebUrl())

src/Presentation/SmartStore.Web/Administration/Scripts/admin.common.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ var Admin = {
108108
}
109109
}
110110
});
111+
},
112+
error: function (xhr, ajaxOptions, thrownError) {
113+
window.clearInterval(interval);
111114
}
112115
});
113116
}

src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/List.cshtml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,6 @@
8383
}
8484
});
8585
86-
function refreshGrid() {
87-
var grid = $("#schedule-tasks-grid").data("tGrid");
88-
grid.ajaxRequest();
89-
}
90-
9186
Admin.TaskWatcher.startWatching({
9287
context: $('#schedule-tasks-grid'),
9388
elementsSelector: '.task-progress',

src/Presentation/SmartStore.Web/Controllers/TaskSchedulerController.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ public ActionResult Sweep()
4444
var prevTaskStart = DateTime.UtcNow;
4545
var count = 0;
4646

47+
return new HttpStatusCodeResult(System.Net.HttpStatusCode.Unauthorized, "YOOO, this is not authorized dude. WTF?!");
48+
4749
foreach (var task in pendingTasks)
4850
{
4951
var elapsedSincePrevTask = DateTime.UtcNow - prevTaskStart;
@@ -80,7 +82,7 @@ public ActionResult Execute(int id /* taskId */)
8082

8183
_taskExecutor.Execute(task);
8284

83-
return Content("Task '{0}' executed".FormatCurrent(task.Name));
85+
return Content("Task '{0}' executed".FormatCurrent(task.Name));
8486
}
8587

8688
}

0 commit comments

Comments
 (0)