1
1
use std:: collections:: HashSet ;
2
2
use std:: collections:: hash_map:: HashMap ;
3
3
use std:: fmt;
4
- use std:: io:: Write ;
4
+ use std:: io:: { self , Write } ;
5
+ use std:: mem;
5
6
use std:: sync:: mpsc:: { channel, Sender , Receiver } ;
6
7
7
8
use crossbeam:: { self , Scope } ;
9
+ use jobserver:: { Acquired , HelperThread } ;
8
10
use term:: color:: YELLOW ;
9
11
10
12
use core:: { PackageId , Target , Profile } ;
11
13
use util:: { Config , DependencyQueue , Fresh , Dirty , Freshness } ;
12
- use util:: { CargoResult , ProcessBuilder , profile, internal} ;
14
+ use util:: { CargoResult , ProcessBuilder , profile, internal, CargoResultExt } ;
13
15
use { handle_error} ;
14
16
15
17
use super :: { Context , Kind , Unit } ;
@@ -21,10 +23,9 @@ use super::job::Job;
21
23
/// actual compilation step of each package. Packages enqueue units of work and
22
24
/// then later on the entire graph is processed and compiled.
23
25
pub struct JobQueue < ' a > {
24
- jobs : usize ,
25
26
queue : DependencyQueue < Key < ' a > , Vec < ( Job , Freshness ) > > ,
26
- tx : Sender < ( Key < ' a > , Message ) > ,
27
- rx : Receiver < ( Key < ' a > , Message ) > ,
27
+ tx : Sender < Message < ' a > > ,
28
+ rx : Receiver < Message < ' a > > ,
28
29
active : usize ,
29
30
pending : HashMap < Key < ' a > , PendingBuild > ,
30
31
compiled : HashSet < & ' a PackageId > ,
@@ -51,36 +52,35 @@ struct Key<'a> {
51
52
}
52
53
53
54
pub struct JobState < ' a > {
54
- tx : Sender < ( Key < ' a > , Message ) > ,
55
- key : Key < ' a > ,
55
+ tx : Sender < Message < ' a > > ,
56
56
}
57
57
58
- enum Message {
58
+ enum Message < ' a > {
59
59
Run ( String ) ,
60
60
Stdout ( String ) ,
61
61
Stderr ( String ) ,
62
- Finish ( CargoResult < ( ) > ) ,
62
+ Token ( io:: Result < Acquired > ) ,
63
+ Finish ( Key < ' a > , CargoResult < ( ) > ) ,
63
64
}
64
65
65
66
impl < ' a > JobState < ' a > {
66
67
pub fn running ( & self , cmd : & ProcessBuilder ) {
67
- let _ = self . tx . send ( ( self . key , Message :: Run ( cmd. to_string ( ) ) ) ) ;
68
+ let _ = self . tx . send ( Message :: Run ( cmd. to_string ( ) ) ) ;
68
69
}
69
70
70
71
pub fn stdout ( & self , out : & str ) {
71
- let _ = self . tx . send ( ( self . key , Message :: Stdout ( out. to_string ( ) ) ) ) ;
72
+ let _ = self . tx . send ( Message :: Stdout ( out. to_string ( ) ) ) ;
72
73
}
73
74
74
75
pub fn stderr ( & self , err : & str ) {
75
- let _ = self . tx . send ( ( self . key , Message :: Stderr ( err. to_string ( ) ) ) ) ;
76
+ let _ = self . tx . send ( Message :: Stderr ( err. to_string ( ) ) ) ;
76
77
}
77
78
}
78
79
79
80
impl < ' a > JobQueue < ' a > {
80
81
pub fn new < ' cfg > ( cx : & Context < ' a , ' cfg > ) -> JobQueue < ' a > {
81
82
let ( tx, rx) = channel ( ) ;
82
83
JobQueue {
83
- jobs : cx. jobs ( ) as usize ,
84
84
queue : DependencyQueue :: new ( ) ,
85
85
tx : tx,
86
86
rx : rx,
@@ -113,56 +113,100 @@ impl<'a> JobQueue<'a> {
113
113
pub fn execute ( & mut self , cx : & mut Context ) -> CargoResult < ( ) > {
114
114
let _p = profile:: start ( "executing the job graph" ) ;
115
115
116
+ // We need to give a handle to the send half of our message queue to the
117
+ // jobserver helper thrad. Unfortunately though we need the handle to be
118
+ // `'static` as that's typically what's required when spawning a
119
+ // thread!
120
+ //
121
+ // To work around this we transmute the `Sender` to a static lifetime.
122
+ // we're only sending "longer living" messages and we should also
123
+ // destroy all references to the channel before this function exits as
124
+ // the destructor for the `helper` object will ensure the associated
125
+ // thread i sno longer running.
126
+ //
127
+ // As a result, this `transmute` to a longer lifetime should be safe in
128
+ // practice.
129
+ let tx = self . tx . clone ( ) ;
130
+ let tx = unsafe {
131
+ mem:: transmute :: < Sender < Message < ' a > > , Sender < Message < ' static > > > ( tx)
132
+ } ;
133
+ let helper = cx. jobserver . clone ( ) . into_helper_thread ( move |token| {
134
+ drop ( tx. send ( Message :: Token ( token) ) ) ;
135
+ } ) . chain_err ( || {
136
+ "failed to create helper thread for jobserver management"
137
+ } ) ?;
138
+
116
139
crossbeam:: scope ( |scope| {
117
- self . drain_the_queue ( cx, scope)
140
+ self . drain_the_queue ( cx, scope, & helper )
118
141
} )
119
142
}
120
143
121
- fn drain_the_queue ( & mut self , cx : & mut Context , scope : & Scope < ' a > )
144
+ fn drain_the_queue ( & mut self ,
145
+ cx : & mut Context ,
146
+ scope : & Scope < ' a > ,
147
+ jobserver_helper : & HelperThread )
122
148
-> CargoResult < ( ) > {
123
149
use std:: time:: Instant ;
124
150
151
+ let mut tokens = Vec :: new ( ) ;
125
152
let mut queue = Vec :: new ( ) ;
126
153
trace ! ( "queue: {:#?}" , self . queue) ;
127
154
128
155
// Iteratively execute the entire dependency graph. Each turn of the
129
156
// loop starts out by scheduling as much work as possible (up to the
130
- // maximum number of parallel jobs). A local queue is maintained
131
- // separately from the main dependency queue as one dequeue may actually
132
- // dequeue quite a bit of work (e.g. 10 binaries in one project).
157
+ // maximum number of parallel jobs we have tokens for). A local queue
158
+ // is maintained separately from the main dependency queue as one
159
+ // dequeue may actually dequeue quite a bit of work (e.g. 10 binaries
160
+ // in one project).
133
161
//
134
162
// After a job has finished we update our internal state if it was
135
163
// successful and otherwise wait for pending work to finish if it failed
136
164
// and then immediately return.
137
165
let mut error = None ;
138
166
let start_time = Instant :: now ( ) ;
139
167
loop {
140
- while error . is_none ( ) && self . active < self . jobs {
141
- if !queue . is_empty ( ) {
142
- let ( key , job , fresh ) = queue . remove ( 0 ) ;
143
- self . run ( key , fresh , job , cx . config , scope ) ? ;
144
- } else if let Some ( ( fresh, key, jobs) ) = self . queue . dequeue ( ) {
145
- let total_fresh = jobs. iter ( ) . fold ( fresh, |fresh, & ( _, f) | {
146
- f. combine ( fresh)
147
- } ) ;
148
- self . pending . insert ( key, PendingBuild {
149
- amt : jobs. len ( ) ,
150
- fresh : total_fresh,
151
- } ) ;
152
- queue . extend ( jobs . into_iter ( ) . map ( | ( job, f) | {
153
- ( key, job, f. combine ( fresh) )
154
- } ) ) ;
155
- } else {
156
- break
168
+ // Dequeue as much work as we can, learning about everything
169
+ // possible that can run. Note that this is also the point where we
170
+ // start requesting job tokens. Each job after the first needs to
171
+ // request a token.
172
+ while let Some ( ( fresh, key, jobs) ) = self . queue . dequeue ( ) {
173
+ let total_fresh = jobs. iter ( ) . fold ( fresh, |fresh, & ( _, f) | {
174
+ f. combine ( fresh)
175
+ } ) ;
176
+ self . pending . insert ( key, PendingBuild {
177
+ amt : jobs. len ( ) ,
178
+ fresh : total_fresh,
179
+ } ) ;
180
+ for ( job, f) in jobs {
181
+ queue . push ( ( key, job, f. combine ( fresh) ) ) ;
182
+ if self . active + queue . len ( ) > 0 {
183
+ jobserver_helper . request_token ( ) ;
184
+ }
157
185
}
158
186
}
187
+
188
+ // Now that we've learned of all possible work that we can execute
189
+ // try to spawn it so long as we've got a jobserver token which says
190
+ // we're able to perform some parallel work.
191
+ while error. is_none ( ) && self . active < tokens. len ( ) + 1 && !queue. is_empty ( ) {
192
+ let ( key, job, fresh) = queue. remove ( 0 ) ;
193
+ self . run ( key, fresh, job, cx. config , scope) ?;
194
+ }
195
+
196
+ // If after all that we're not actually running anything then we're
197
+ // done!
159
198
if self . active == 0 {
160
199
break
161
200
}
162
201
163
- let ( key, msg) = self . rx . recv ( ) . unwrap ( ) ;
202
+ // And finally, before we block waiting for the next event, drop any
203
+ // excess tokens we may have accidentally acquired. Due to how our
204
+ // jobserver interface is architected we may acquire a token that we
205
+ // don't actually use, and if this happens just relinquish it back
206
+ // to the jobserver itself.
207
+ tokens. truncate ( self . active - 1 ) ;
164
208
165
- match msg {
209
+ match self . rx . recv ( ) . unwrap ( ) {
166
210
Message :: Run ( cmd) => {
167
211
cx. config . shell ( ) . verbose ( |c| c. status ( "Running" , & cmd) ) ?;
168
212
}
@@ -176,9 +220,13 @@ impl<'a> JobQueue<'a> {
176
220
writeln ! ( cx. config. shell( ) . err( ) , "{}" , err) ?;
177
221
}
178
222
}
179
- Message :: Finish ( result) => {
223
+ Message :: Finish ( key , result) => {
180
224
info ! ( "end: {:?}" , key) ;
181
225
self . active -= 1 ;
226
+ if self . active > 0 {
227
+ assert ! ( tokens. len( ) > 0 ) ;
228
+ drop ( tokens. pop ( ) ) ;
229
+ }
182
230
match result {
183
231
Ok ( ( ) ) => self . finish ( key, cx) ?,
184
232
Err ( e) => {
@@ -198,6 +246,11 @@ impl<'a> JobQueue<'a> {
198
246
}
199
247
}
200
248
}
249
+ Message :: Token ( acquired_token) => {
250
+ tokens. push ( acquired_token. chain_err ( || {
251
+ "failed to acquire jobserver token"
252
+ } ) ?) ;
253
+ }
201
254
}
202
255
}
203
256
@@ -244,9 +297,8 @@ impl<'a> JobQueue<'a> {
244
297
scope. spawn ( move || {
245
298
let res = job. run ( fresh, & JobState {
246
299
tx : my_tx. clone ( ) ,
247
- key : key,
248
300
} ) ;
249
- my_tx. send ( ( key , Message :: Finish ( res) ) ) . unwrap ( ) ;
301
+ my_tx. send ( Message :: Finish ( key , res) ) . unwrap ( ) ;
250
302
} ) ;
251
303
252
304
// Print out some nice progress information
0 commit comments