1111use std:: {
1212 path:: { Path , PathBuf } ,
1313 process,
14+ sync:: atomic:: { AtomicUsize , Ordering } ,
1415} ;
1516
1617use crate :: Installation ;
1718use container:: Container ;
1819use itertools:: Itertools ;
20+ use rayon:: iter:: { IntoParallelRefIterator , ParallelIterator } ;
1921use serde:: Deserialize ;
2022use thiserror:: Error ;
2123use tracing:: { error, warn} ;
@@ -100,13 +102,19 @@ impl TriggerScope<'_> {
100102}
101103
102104/// Condensed type for loaded triggers with scope and executor
103- #[ derive( Debug ) ]
104- pub ( super ) struct TriggerRunner < ' a > {
105- scope : TriggerScope < ' a > ,
105+ pub ( super ) struct TriggerRunner {
106106 trigger : CompiledHandler ,
107107}
108108
109- /// Load all triggers matching the given scope and staging filesystem
109+ /// Progress callback handler
110+ #[ derive( Debug , Clone ) ]
111+ pub struct Progress < ' a > {
112+ pub completed : u64 ,
113+ pub item : & ' a str ,
114+ }
115+
116+ /// Load all triggers matching the given scope and staging filesystem, return in batches
117+ /// suitable for concurrent/parallel processing.
110118///
111119/// # Arguments
112120///
@@ -115,7 +123,7 @@ pub(super) struct TriggerRunner<'a> {
115123pub ( super ) fn triggers < ' a > (
116124 scope : TriggerScope < ' a > ,
117125 fstree : & vfs:: tree:: Tree < PendingFile > ,
118- ) -> Result < Vec < TriggerRunner < ' a > > , Error > {
126+ ) -> Result < Vec < Vec < TriggerRunner > > , Error > {
119127 // Pre-calculate trigger root path once
120128 let trigger_root = {
121129 let mut path = PathBuf :: with_capacity ( 50 ) ;
@@ -145,57 +153,125 @@ pub(super) fn triggers<'a>(
145153 // Load trigger collection, process all the paths, convert to scoped TriggerRunner vec
146154 let mut collection = triggers:: Collection :: new ( triggers. iter ( ) ) ?;
147155 collection. process_paths ( fstree. iter ( ) . map ( |m| m. to_string ( ) ) ) ;
148- let computed_commands = collection
149- . bake ( ) ?
156+ let batches = collection
157+ . bake_in_stages ( ) ?
150158 . into_iter ( )
151- . map ( |trigger| TriggerRunner { scope , trigger } )
159+ . map ( |batch| batch . into_iter ( ) . map ( | trigger| TriggerRunner { trigger } ) . collect_vec ( ) )
152160 . collect_vec ( ) ;
153- Ok ( computed_commands )
161+ Ok ( batches )
154162}
155163
156- impl TriggerRunner < ' _ > {
157- pub fn handler ( & self ) -> & Handler {
158- self . trigger . handler ( )
164+ /// Execute triggers based on TriggerScope
165+ ///
166+ /// Execute either transaction or system scope triggers using container sandboxing as necessary
167+ pub fn execute_triggers (
168+ scope : TriggerScope < ' _ > ,
169+ triggers : & [ Vec < TriggerRunner > ] ,
170+ on_progress : impl Fn ( Progress < ' _ > ) + Send + Sync ,
171+ ) -> Result < ( ) , Error > {
172+ match scope {
173+ scope @ TriggerScope :: Transaction ( install, _) => {
174+ execute_transaction_triggers ( install, scope, triggers, & on_progress) ?;
175+ }
176+ scope @ TriggerScope :: System ( install, _) => {
177+ execute_system_triggers ( install, scope, triggers, & on_progress) ?;
178+ }
179+ } ;
180+
181+ Ok ( ( ) )
182+ }
183+
184+ /// Execute transaction triggers
185+ ///
186+ /// Transaction triggers are run via sandboxing ([`container::Container`]) to limit their
187+ /// system view, and limit write access. Each batch of triggers are executed in parallel
188+ /// to speed up execution time.
189+ fn execute_transaction_triggers < P > (
190+ install : & Installation ,
191+ scope : TriggerScope < ' _ > ,
192+ triggers : & [ Vec < TriggerRunner > ] ,
193+ on_progress : P ,
194+ ) -> Result < ( ) , Error >
195+ where
196+ P : Fn ( Progress < ' _ > ) + Send + Sync ,
197+ {
198+ // TODO: Add caching support via /var/
199+ let isolation = Container :: new ( install. isolation_dir ( ) )
200+ . networking ( false )
201+ . bind_ro ( scope. host_path ( "etc" ) , "/etc" )
202+ . bind_rw ( scope. guest_path ( "usr" ) , "/usr" )
203+ . work_dir ( "/" ) ;
204+
205+ isolation. run ( || execute_triggers_directly ( triggers, & on_progress) ) ?;
206+
207+ Ok ( ( ) )
208+ }
209+
210+ /// Execute system triggers
211+ ///
212+ /// System triggers will execute without any sandboxing when moss is used directly against the
213+ /// live root filesystem, and will force sandboxing when using a non-`/` root (such as using the
214+ /// `-D argument with `moss install`). Each batch of triggers is executed in parallel to speed up
215+ /// execution time.
216+ fn execute_system_triggers < P > (
217+ install : & Installation ,
218+ scope : TriggerScope < ' _ > ,
219+ triggers : & [ Vec < TriggerRunner > ] ,
220+ on_progress : P ,
221+ ) -> Result < ( ) , Error >
222+ where
223+ P : Fn ( Progress < ' _ > ) + Send + Sync ,
224+ {
225+ // OK, if the root == `/` then we can run directly, otherwise we need to containerise with RW.
226+ if install. root . to_string_lossy ( ) == "/" {
227+ execute_triggers_directly ( triggers, on_progress) ?;
228+ } else {
229+ let isolation = Container :: new ( install. isolation_dir ( ) )
230+ . networking ( false )
231+ . bind_rw ( scope. host_path ( "etc" ) , "/etc" )
232+ . bind_rw ( scope. guest_path ( "usr" ) , "/usr" )
233+ . work_dir ( "/" ) ;
234+
235+ isolation. run ( || execute_triggers_directly ( triggers, & on_progress) ) ?;
159236 }
237+ Ok ( ( ) )
238+ }
160239
161- /// Execute a trigger, taking care to account for the transaction scope and client scope
162- ///
163- /// All transaction triggers are run via sandboxing ([`container::Container`]) to limit their
164- /// system view, and limit write access.
165- /// System triggers will execute without any sandboxing when moss is used directly against the
166- /// live root filesystem, and will force sandboxing when using a non-`/` root (such as using the
167- /// `-D argument with `moss install`)
168- pub fn execute ( & self ) -> Result < ( ) , Error > {
169- match self . scope {
170- TriggerScope :: Transaction ( install, _) => {
171- // TODO: Add caching support via /var/
172- let isolation = Container :: new ( install. isolation_dir ( ) )
173- . networking ( false )
174- . bind_ro ( self . scope . host_path ( "etc" ) , "/etc" )
175- . bind_rw ( self . scope . guest_path ( "usr" ) , "/usr" )
176- . work_dir ( "/" ) ;
177-
178- Ok ( isolation. run ( || execute_trigger_directly ( & self . trigger ) ) ?)
179- }
180- TriggerScope :: System ( install, _) => {
181- // OK, if the root == `/` then we can run directly, otherwise we need to containerise with RW.
182- if install. root . to_string_lossy ( ) == "/" {
183- Ok ( execute_trigger_directly ( & self . trigger ) ?)
184- } else {
185- let isolation = Container :: new ( install. isolation_dir ( ) )
186- . networking ( false )
187- . bind_rw ( self . scope . host_path ( "etc" ) , "/etc" )
188- . bind_rw ( self . scope . guest_path ( "usr" ) , "/usr" )
189- . work_dir ( "/" ) ;
190-
191- Ok ( isolation. run ( || execute_trigger_directly ( & self . trigger ) ) ?)
192- }
193- }
194- }
240+ impl TriggerRunner {
241+ pub fn handler ( & self ) -> & Handler {
242+ self . trigger . handler ( )
195243 }
196244}
197245
198246/// Internal executor for triggers.
247+ fn execute_triggers_directly < P > ( triggers : & [ Vec < TriggerRunner > ] , on_progress : P ) -> Result < ( ) , Error >
248+ where
249+ P : Fn ( Progress < ' _ > ) + Send + Sync ,
250+ {
251+ let rayon_runtime = rayon:: ThreadPoolBuilder :: new ( ) . build ( ) . expect ( "rayon runtime" ) ;
252+
253+ let counter = AtomicUsize :: new ( 0 ) ;
254+
255+ rayon_runtime. install ( || {
256+ triggers. iter ( ) . try_for_each ( |batch| {
257+ batch. par_iter ( ) . try_for_each ( |trigger| {
258+ let res = execute_trigger_directly ( & trigger. trigger ) ;
259+ let completed = counter. fetch_add ( 1 , Ordering :: Relaxed ) ;
260+ ( on_progress) ( Progress {
261+ completed : completed as u64 ,
262+ item : match trigger. handler ( ) {
263+ Handler :: Run { run, .. } => run,
264+ Handler :: Delete { .. } => "delete operation" ,
265+ } ,
266+ } ) ;
267+ res
268+ } )
269+ } )
270+ } ) ?;
271+ Ok ( ( ) )
272+ }
273+
274+ /// Internal executor for individual triggers.
199275fn execute_trigger_directly ( trigger : & CompiledHandler ) -> Result < ( ) , Error > {
200276 match trigger. handler ( ) {
201277 Handler :: Run { run, args } => {
0 commit comments