|
| 1 | +using Cronos; |
| 2 | +using StackExchange.Redis; |
1 | 3 | using System.Collections.Concurrent; |
2 | 4 | using System.Linq.Expressions; |
| 5 | +using System.Threading.Tasks; |
3 | 6 | using System.Threading.Tasks.Dataflow; |
4 | | -using Cronos; |
5 | | -using StackExchange.Redis; |
6 | 7 |
|
7 | 8 | namespace Core.TaskProcessor; |
8 | 9 |
|
@@ -128,9 +129,17 @@ public async Task<string> EnqueueBatchAsync(string queue, string tenant, List<Ta |
128 | 129 | foreach (var q in push) |
129 | 130 | { |
130 | 131 | tra.SortedSetAddAsync(Prefix("queues"), q.Key, DateTimeOffset.UtcNow.ToUnixTimeSeconds()); |
131 | | - tra.ListLeftPushAsync(Prefix($"queue:{q.Key}"), q.Value.ToArray()); |
132 | | - tra.PublishAsync(RedisChannel.Literal(Prefix($"queue:{q.Key}:event")), "fetch"); |
133 | 132 |
|
| 133 | + if (q.Key.StartsWith("fair_")) |
| 134 | + { |
| 135 | + tra.HashIncrementAsync(Prefix($"queue:{q.Key}:fairness"), tenant, q.Value.Count); |
| 136 | + tra.ListLeftPushAsync(Prefix($"queue:{q.Key}:{tenant}"), q.Value.ToArray()); |
| 137 | + } |
| 138 | + else |
| 139 | + tra.ListLeftPushAsync(Prefix($"queue:{q.Key}"), q.Value.ToArray()); |
| 140 | + |
| 141 | + |
| 142 | + tra.PublishAsync(RedisChannel.Literal(Prefix($"queue:{q.Key}:event")), "fetch"); |
134 | 143 | } |
135 | 144 |
|
136 | 145 | #pragma warning restore CS4014 |
@@ -164,7 +173,15 @@ public async Task<string> EnqueueTaskAsync(string queue, string tenant, TaskData |
164 | 173 | else |
165 | 174 | { |
166 | 175 | tra.SortedSetAddAsync(Prefix("queues"), q, DateTimeOffset.UtcNow.ToUnixTimeSeconds()); |
167 | | - tra.ListLeftPushAsync(Prefix($"queue:{q}"), taskId); |
| 176 | + |
| 177 | + if (queue.StartsWith("fair_")) |
| 178 | + { |
| 179 | + tra.HashIncrementAsync(Prefix($"queue:{q}:fairness"), tenant); |
| 180 | + tra.ListLeftPushAsync(Prefix($"queue:{q}:{tenant}"), taskId); |
| 181 | + } |
| 182 | + else |
| 183 | + tra.ListLeftPushAsync(Prefix($"queue:{q}"), taskId); |
| 184 | + |
168 | 185 | tra.PublishAsync(RedisChannel.Literal(Prefix($"queue:{q}:event")), "fetch"); |
169 | 186 | } |
170 | 187 |
|
@@ -255,20 +272,50 @@ public async Task<bool> FetchAsync() |
255 | 272 |
|
256 | 273 | var res = await db.ScriptEvaluateAsync($@" |
257 | 274 | for i, queue in ipairs(KEYS) do |
258 | | -local taskId = redis.call('rpoplpush', queue, queue.."":checkout""); |
259 | | -if taskId then |
260 | | - local invis = redis.call('time')[1] + ARGV[1]; |
261 | | - redis.call('zadd', queue.."":pushback"", invis, taskId); |
262 | | - local taskData = redis.call('hgetall', ""{Prefix("task:")}""..taskId); |
263 | | - local batchId = redis.call('hget', ""{Prefix("task:")}""..taskId, ""batch""); |
264 | 275 |
|
265 | | - if batchId then |
266 | | - local batchData = redis.call('hgetall', ""{Prefix("batch:")}""..batchId); |
267 | | - return {{queue, taskId, taskData, batchData}}; |
268 | | - end; |
| 276 | +if string.find(queue, 'fair_') then |
| 277 | + local tenant = redis.call('hrandfield', queue.."":fairness""); |
| 278 | +
|
| 279 | + if tenant then |
| 280 | + local taskId = redis.call('rpoplpush', queue.."":""..tenant, queue.."":checkout""); |
| 281 | +
|
| 282 | + if taskId then |
| 283 | + local ctr = redis.call('hincrby', queue.."":fairness"", tenant, -1); |
| 284 | +
|
| 285 | + if ctr <= 0 then |
| 286 | + redis.call('hdel', queue.."":fairness"", tenant); |
| 287 | + end; |
269 | 288 |
|
270 | | - return {{queue, taskId, taskData}}; |
| 289 | + local invis = redis.call('time')[1] + ARGV[1]; |
| 290 | + redis.call('zadd', queue.."":pushback"", invis, taskId); |
| 291 | + local taskData = redis.call('hgetall', ""{Prefix("task:")}""..taskId); |
| 292 | + local batchId = redis.call('hget', ""{Prefix("task:")}""..taskId, ""batch""); |
| 293 | +
|
| 294 | + if batchId then |
| 295 | + local batchData = redis.call('hgetall', ""{Prefix("batch:")}""..batchId); |
| 296 | + return {{queue, taskId, taskData, batchData}}; |
| 297 | + end; |
| 298 | +
|
| 299 | + return {{queue, taskId, taskData}}; |
| 300 | + end; |
| 301 | + end; |
| 302 | +else |
| 303 | + local taskId = redis.call('rpoplpush', queue, queue.."":checkout""); |
| 304 | + if taskId then |
| 305 | + local invis = redis.call('time')[1] + ARGV[1]; |
| 306 | + redis.call('zadd', queue.."":pushback"", invis, taskId); |
| 307 | + local taskData = redis.call('hgetall', ""{Prefix("task:")}""..taskId); |
| 308 | + local batchId = redis.call('hget', ""{Prefix("task:")}""..taskId, ""batch""); |
| 309 | +
|
| 310 | + if batchId then |
| 311 | + local batchData = redis.call('hgetall', ""{Prefix("batch:")}""..batchId); |
| 312 | + return {{queue, taskId, taskData, batchData}}; |
| 313 | + end; |
| 314 | +
|
| 315 | + return {{queue, taskId, taskData}}; |
| 316 | + end; |
271 | 317 | end; |
| 318 | +
|
272 | 319 | end |
273 | 320 | ", _queueKeys, new RedisValue[] { (long)_options.Retention.TotalSeconds }); |
274 | 321 |
|
@@ -951,7 +998,15 @@ public async Task<bool> TriggerScheduleAsync(string id) |
951 | 998 | }, CommandFlags.FireAndForget); |
952 | 999 |
|
953 | 1000 | // enqueue |
954 | | - tra.ListLeftPushAsync(Prefix($"queue:{queue}"), newTaskId, flags: CommandFlags.FireAndForget); |
| 1001 | + |
| 1002 | + if (queue.StartsWith("fair_")) |
| 1003 | + { |
| 1004 | + tra.HashIncrementAsync(Prefix($"queue:{queue}:fairness"), tenant); |
| 1005 | + tra.ListLeftPushAsync(Prefix($"queue:{queue}:{tenant}"), newTaskId, flags: CommandFlags.FireAndForget); |
| 1006 | + } |
| 1007 | + else |
| 1008 | + tra.ListLeftPushAsync(Prefix($"queue:{queue}"), newTaskId, flags: CommandFlags.FireAndForget); |
| 1009 | + |
955 | 1010 | tra.SortedSetAddAsync(Prefix("queues"), queue, DateTimeOffset.UtcNow.ToUnixTimeSeconds(), |
956 | 1011 | CommandFlags.FireAndForget); |
957 | 1012 | tra.PublishAsync(RedisChannel.Literal(Prefix($"queue:{queue}:event")), "fetch", CommandFlags.FireAndForget); |
@@ -1163,7 +1218,8 @@ public async Task<QueueInfo> GetQueueAsync(string name) |
1163 | 1218 | return new QueueInfo |
1164 | 1219 | { |
1165 | 1220 | Name = name, |
1166 | | - Length = await db.ListLengthAsync(Prefix($"queue:{name}")), |
| 1221 | + // TODO: global tracking of fair queue length |
| 1222 | + Length = name.StartsWith("fair_") ? -1 : await db.ListLengthAsync(Prefix($"queue:{name}")), |
1167 | 1223 | Checkout = await db.ListLengthAsync(Prefix($"queue:{name}:checkout")), |
1168 | 1224 | Pushback = await db.SortedSetLengthAsync(Prefix($"queue:{name}:pushback")), |
1169 | 1225 | Deadletter = await db.SortedSetLengthAsync(Prefix($"queue:{name}:deadletter")) |
|
0 commit comments