11use std:: {
2+ collections:: HashMap ,
23 process:: { Child , Command } ,
34 string:: FromUtf8Error ,
45 sync:: atomic:: { AtomicU32 , AtomicU64 , Ordering } ,
@@ -15,6 +16,9 @@ use crate::github_api::{get_idle_runners, spawn_runner};
1516
1617mod github_api;
1718
19+ const MAX_SPAWN_RETRIES : u32 = 10 ;
20+ /// How long the loop will sleep in milliseconds.
21+ const LOOP_SLEEP : u64 = 500 ;
1822static RUNNER_ID : AtomicU64 = AtomicU64 :: new ( 0 ) ;
1923static EXITING : AtomicU32 = AtomicU32 :: new ( 0 ) ;
2024
@@ -143,7 +147,7 @@ enum SpawnRunnerError {
143147#[ cfg( target_os = "linux" ) ]
144148const OS_TAG : & str = "Linux" ;
145149
146- #[ derive( Clone , Debug , PartialEq ) ]
150+ #[ derive( Clone , Copy , Debug , PartialEq , Eq , Hash ) ]
147151enum ContainerType {
148152 Builder ,
149153 Runner ,
@@ -187,7 +191,7 @@ impl Iterator for ContainerTypeIterator {
187191 None
188192 }
189193 } ;
190- self . current . clone ( )
194+ self . current
191195 }
192196}
193197
@@ -207,6 +211,30 @@ struct DockerContainer {
207211 container_type : ContainerType ,
208212}
209213
214+ #[ derive( Debug , Default ) ]
215+ /// Store the number of retries per container type
216+ struct Retries {
217+ t : HashMap < ContainerType , u32 > ,
218+ }
219+
220+ impl Retries {
221+ /// Increases the number of retries and quits if it reaches `MAX_SPAWN_RETRIES`.
222+ fn inc_and_check ( & mut self , t : ContainerType ) {
223+ let value = self . t . entry ( t) . or_insert ( 0 ) ;
224+ * value += 1 ;
225+ if * value > MAX_SPAWN_RETRIES {
226+ println ! (
227+ "We had {value} many times to spawn a runner/builder ({t:?}). It is not happening."
228+ ) ;
229+ std:: process:: exit ( -1 ) ;
230+ }
231+ }
232+
233+ fn reset ( & mut self , t : ContainerType ) {
234+ self . t . entry ( t) . insert_entry ( 0 ) ;
235+ }
236+ }
237+
210238fn main ( ) -> anyhow:: Result < ( ) > {
211239 env_logger:: init ( ) ;
212240 info ! ( "Starting monitor for selfhosted docker-based github runners!" ) ;
@@ -232,6 +260,7 @@ fn main() -> anyhow::Result<()> {
232260 let mut running_containers: Vec < DockerContainer > = vec ! [ ] ;
233261 // Todo: implement something to reserve devices for the duration of the docker run child process.
234262
263+ let mut retries = Retries :: default ( ) ;
235264 loop {
236265 let exiting = EXITING . load ( Ordering :: Relaxed ) ;
237266 for container_type in ContainerType :: iter ( ) {
@@ -254,13 +283,17 @@ fn main() -> anyhow::Result<()> {
254283 } ;
255284
256285 match spawn_runner ( config) {
257- Ok ( container) => running_containers. push ( container) ,
286+ Ok ( container) => {
287+ retries. reset ( container_type) ;
288+ running_containers. push ( container)
289+ }
258290 Err ( SpawnRunnerError :: GhApiError ( _, message) )
259291 if message. contains ( "gh: Already exists" ) =>
260292 {
261293 info ! ( "Runner name already taken - Will retry with new name later" )
262294 }
263295 Err ( e) => {
296+ retries. inc_and_check ( container_type) ;
264297 error ! ( "Failed to spawn JIT runner: {e:?}" ) ;
265298 }
266299 }
@@ -319,7 +352,7 @@ fn main() -> anyhow::Result<()> {
319352 }
320353
321354 running_containers = still_running;
322- thread:: sleep ( Duration :: from_millis ( 500 ) ) ;
355+ thread:: sleep ( Duration :: from_millis ( LOOP_SLEEP ) ) ;
323356 }
324357
325358 info ! ( "Exiting...." ) ;
0 commit comments