feat(server): properly reschedule jobs after polling
							parent
							
								
									c57de4d8ee
								
							
						
					
					
						commit
						0a5c4295e0
					
				|  | @ -8,12 +8,16 @@ import util | ||||||
| 
 | 
 | ||||||
| struct BuildJob { | struct BuildJob { | ||||||
| pub: | pub: | ||||||
| 	// Earliest point this | 	// Next timestamp from which point this job is allowed to be executed | ||||||
| 	timestamp time.Time | 	timestamp time.Time | ||||||
| 	config    BuildConfig | 	// Required for calculating next timestamp after having pop'ed a job | ||||||
|  | 	ce CronExpression | ||||||
|  | 	// Actual build config sent to the agent | ||||||
|  | 	config BuildConfig | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Overloaded operator for comparing ScheduledBuild objects | // Allows BuildJob structs to be sorted according to their timestamp in | ||||||
|  | // MinHeaps | ||||||
| fn (r1 BuildJob) < (r2 BuildJob) bool { | fn (r1 BuildJob) < (r2 BuildJob) bool { | ||||||
| 	return r1.timestamp < r2.timestamp | 	return r1.timestamp < r2.timestamp | ||||||
| } | } | ||||||
|  | @ -39,7 +43,9 @@ pub fn new_job_queue(default_schedule CronExpression, default_base_image string) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // insert a new job into the queue for a given target on an architecture. | // insert a new target's job into the queue for the given architecture. This | ||||||
|  | // job will then be endlessly rescheduled after being pop'ed, unless removed | ||||||
|  | // explicitely. | ||||||
| pub fn (mut q BuildJobQueue) insert(target Target, arch string) ! { | pub fn (mut q BuildJobQueue) insert(target Target, arch string) ! { | ||||||
| 	lock q.mutex { | 	lock q.mutex { | ||||||
| 		if arch !in q.queues { | 		if arch !in q.queues { | ||||||
|  | @ -58,6 +64,7 @@ pub fn (mut q BuildJobQueue) insert(target Target, arch string) ! { | ||||||
| 
 | 
 | ||||||
| 		job := BuildJob{ | 		job := BuildJob{ | ||||||
| 			timestamp: timestamp | 			timestamp: timestamp | ||||||
|  | 			ce: ce | ||||||
| 			config: BuildConfig{ | 			config: BuildConfig{ | ||||||
| 				target_id: target.id | 				target_id: target.id | ||||||
| 				kind: target.kind | 				kind: target.kind | ||||||
|  | @ -69,10 +76,25 @@ pub fn (mut q BuildJobQueue) insert(target Target, arch string) ! { | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		dump(job) | ||||||
| 		q.queues[arch].insert(job) | 		q.queues[arch].insert(job) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // reschedule the given job by calculating the next timestamp and re-adding it | ||||||
|  | // to its respective queue. This function is called by the pop functions | ||||||
|  | // *after* having pop'ed the job. | ||||||
|  | fn (mut q BuildJobQueue) reschedule(job BuildJob, arch string) ! { | ||||||
|  | 	new_timestamp := job.ce.next_from_now()! | ||||||
|  | 
 | ||||||
|  | 	new_job := BuildJob{ | ||||||
|  | 		...job | ||||||
|  | 		timestamp: new_timestamp | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	q.queues[arch].insert(new_job) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // peek shows the first job for the given architecture that's ready to be | // peek shows the first job for the given architecture that's ready to be | ||||||
| // executed, if present. | // executed, if present. | ||||||
| pub fn (q &BuildJobQueue) peek(arch string) ?BuildJob { | pub fn (q &BuildJobQueue) peek(arch string) ?BuildJob { | ||||||
|  | @ -99,10 +121,17 @@ pub fn (mut q BuildJobQueue) pop(arch string) ?BuildJob { | ||||||
| 			return none | 			return none | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		job := q.queues[arch].peek() or { return none } | 		mut job := q.queues[arch].peek() or { return none } | ||||||
| 
 | 
 | ||||||
| 		if job.timestamp < time.now() { | 		if job.timestamp < time.now() { | ||||||
| 			return q.queues[arch].pop() | 			job = q.queues[arch].pop()? | ||||||
|  | 
 | ||||||
|  | 			// TODO how do we handle this properly? Is it even possible for a | ||||||
|  | 			// cron expression to not return a next time if it's already been | ||||||
|  | 			// used before? | ||||||
|  | 			q.reschedule(job, arch) or {} | ||||||
|  | 
 | ||||||
|  | 			return job | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -119,10 +148,15 @@ pub fn (mut q BuildJobQueue) pop_n(arch string, n int) []BuildJob { | ||||||
| 		mut out := []BuildJob{} | 		mut out := []BuildJob{} | ||||||
| 
 | 
 | ||||||
| 		for out.len < n { | 		for out.len < n { | ||||||
| 			job := q.queues[arch].peek() or { break } | 			mut job := q.queues[arch].peek() or { break } | ||||||
| 
 | 
 | ||||||
| 			if job.timestamp < time.now() { | 			if job.timestamp < time.now() { | ||||||
| 				out << q.queues[arch].pop() or { break } | 				job = q.queues[arch].pop() or { break } | ||||||
|  | 
 | ||||||
|  | 				// TODO idem | ||||||
|  | 				q.reschedule(job, arch) or {} | ||||||
|  | 
 | ||||||
|  | 				out << job | ||||||
| 			} else { | 			} else { | ||||||
| 				break | 				break | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | @ -6,8 +6,8 @@ import web.response { new_data_response, new_response } | ||||||
| // import util | // import util | ||||||
| // import models { BuildLog, BuildLogFilter } | // import models { BuildLog, BuildLogFilter } | ||||||
| 
 | 
 | ||||||
| ['/api/v1/builds/poll'; auth; get] | ['/api/v1/jobs/poll'; auth; get] | ||||||
| fn (mut app App) v1_poll_build_queue() web.Result { | fn (mut app App) v1_poll_job_queue() web.Result { | ||||||
| 	arch := app.query['arch'] or { | 	arch := app.query['arch'] or { | ||||||
| 		return app.json(.bad_request, new_response('Missing arch query arg.')) | 		return app.json(.bad_request, new_response('Missing arch query arg.')) | ||||||
| 	} | 	} | ||||||
|  | @ -17,7 +17,7 @@ fn (mut app App) v1_poll_build_queue() web.Result { | ||||||
| 	} | 	} | ||||||
| 	max := max_str.int() | 	max := max_str.int() | ||||||
| 
 | 
 | ||||||
| 	mut out := app.job_queue.pop_n(arch, max) | 	mut out := app.job_queue.pop_n(arch, max).map(it.config) | ||||||
| 
 | 
 | ||||||
| 	return app.json(.ok, new_data_response(out)) | 	return app.json(.ok, new_data_response(out)) | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue