-
Notifications
You must be signed in to change notification settings - Fork 515
Expand file tree
/
Copy pathcs_apps.cpp
More file actions
488 lines (445 loc) · 14.4 KB
/
Copy pathcs_apps.cpp
File metadata and controls
488 lines (445 loc) · 14.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
// This file is part of BOINC.
// https://boinc.berkeley.edu
// Copyright (C) 2024 University of California
//
// BOINC is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
//
// BOINC is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with BOINC. If not, see <http://www.gnu.org/licenses/>.
// The "policy" part of task execution is here.
// The "mechanism" part is in app.cpp
//
#include "cpp.h"
#ifdef _WIN32
#include "boinc_win.h"
#else
#include "config.h"
#include <cassert>
#include <csignal>
#endif
#include <algorithm>
#include "error_numbers.h"
#include "filesys.h"
#include "md5_file.h"
#include "shmem.h"
#include "util.h"
#include "url.h"
#include "client_msgs.h"
#include "client_state.h"
#include "file_names.h"
#include "log_flags.h"
#include "project.h"
#include "result.h"
using std::vector;
// Clean up after finished apps.
// Called every second from the main polling loop.
//
bool CLIENT_STATE::handle_finished_apps() {
ACTIVE_TASK* atp;
bool action = false;
static double last_time = 0;
if (!clock_change && now - last_time < HANDLE_FINISHED_APPS_PERIOD) return false;
last_time = now;
vector<ACTIVE_TASK*>::iterator iter;
iter = active_tasks.active_tasks.begin();
while (iter != active_tasks.active_tasks.end()) {
atp = *iter;
switch (atp->task_state()) {
case PROCESS_EXITED:
case PROCESS_WAS_SIGNALED:
case PROCESS_EXIT_UNKNOWN:
case PROCESS_COULDNT_START:
case PROCESS_ABORTED:
if (log_flags.task) {
msg_printf(atp->wup->project, MSG_INFO,
"Computation for task %s finished", atp->result->name
);
}
app_finished(*atp);
if (!action) {
adjust_rec(); // update REC before erasing ACTIVE_TASK
}
iter = active_tasks.active_tasks.erase(iter);
delete atp;
set_client_state_dirty("handle_finished_apps");
// the following is critical; otherwise the result is
// still in the "scheduled" list and enforce_schedule()
// will try to run it again.
//
request_schedule_cpus("handle_finished_apps");
action = true;
break;
default:
++iter;
}
}
return action;
}
// Handle a task that has finished.
// Mark its output files as present, and delete scratch files.
// Don't delete input files because they might be shared with other WUs.
// Update state of result record.
//
int CLIENT_STATE::app_finished(ACTIVE_TASK& at) {
RESULT* rp = at.result;
bool had_error = false;
#ifndef SIM
FILE_INFO* fip;
char path[MAXPATHLEN];
int retval;
double size;
// scan the output files, check if missing or too big.
// Don't bother doing this if result was aborted via GUI or by project
//
switch (rp->exit_status) {
case EXIT_ABORTED_VIA_GUI:
case EXIT_ABORTED_BY_PROJECT:
break;
default:
for (const FILE_REF& fref: rp->output_files) {
fip = fref.file_info;
if (fip->uploaded) continue;
get_pathname(fip, path, sizeof(path));
retval = file_size(path, size);
if (retval) {
if (fref.optional) {
fip->upload_urls.clear();
continue;
}
// an output file is unexpectedly absent.
//
fip->status = retval;
had_error = true;
msg_printf(
rp->project, MSG_INFO,
"Output file %s for task %s absent",
fip->name, rp->name
);
} else if (size > fip->max_nbytes) {
// Note: this is only checked when the application finishes.
// The total disk space is checked while the application is running.
//
msg_printf(
rp->project, MSG_INFO,
"Output file %s for task %s exceeds size limit.",
fip->name, rp->name
);
msg_printf(
rp->project, MSG_INFO,
"File size: %f bytes. Limit: %f bytes",
size, fip->max_nbytes
);
fip->delete_file();
fip->status = ERR_FILE_TOO_BIG;
had_error = true;
} else {
if (!fip->uploadable() && !fip->sticky) {
fip->delete_file(); // sets status to NOT_PRESENT
} else {
retval = 0;
if (fip->gzip_when_done) {
retval = fip->gzip();
}
if (!retval) {
retval = md5_file(path, fip->md5_cksum, fip->nbytes);
}
if (retval) {
fip->status = retval;
had_error = true;
} else {
fip->status = FILE_PRESENT;
}
}
}
}
}
#endif
if (rp->exit_status != 0) {
had_error = true;
}
if (had_error) {
switch (rp->exit_status) {
case EXIT_ABORTED_VIA_GUI:
case EXIT_ABORTED_BY_PROJECT:
case EXIT_OVERDUE_EXCEEDED:
rp->set_state(RESULT_ABORTED, "CS::app_finished");
break;
default:
rp->set_state(RESULT_COMPUTE_ERROR, "CS::app_finished");
}
rp->project->njobs_error++;
} else {
#ifdef SIM
rp->set_state(RESULT_FILES_UPLOADED, "CS::app_finished");
rp->set_ready_to_report();
rp->completed_time = now;
#else
rp->set_state(RESULT_FILES_UPLOADING, "CS::app_finished");
rp->append_log_record();
#endif
rp->project->update_duration_correction_factor(&at);
rp->project->njobs_success++;
}
double elapsed_time = now - rec_interval_start;
work_fetch.accumulate_inst_sec(&at, elapsed_time);
rp->project->pwf.request_if_idle_and_uploading = true;
// set this to allow work fetch if idle instance,
// even before upload finishes
return 0;
}
// Check whether the input and app version files for a result are
// marked as FILE_PRESENT.
// If check_size is set, also check whether they exist and have the right size.
// (Side-effect: files with a size mismatch will be deleted.)
// Side effect: files with size mismatch are deleted.
//
// If fipp is nonzero, return a pointer to offending FILE_INFO on error
//
// Called from:
// CLIENT_STATE::update_results (with check_size=false)
// to transition result from DOWNLOADING to DOWNLOADED.
// ACTIVE_TASK::start() (with check_size=true)
// to check files before running a task
//
int CLIENT_STATE::task_files_present(
RESULT* rp, bool check_size, FILE_INFO** fipp
) {
WORKUNIT* wup = rp->wup;
FILE_INFO* fip;
APP_VERSION* avp = rp->avp;
int retval, ret = 0;
for (const FILE_REF &fref: avp->app_files) {
fip = fref.file_info;
if (fip->status != FILE_PRESENT) {
if (fipp) *fipp = fip;
ret = ERR_FILE_MISSING;
} else if (check_size) {
retval = fip->check_size();
if (retval) {
if (fipp) *fipp = fip;
ret = retval;
}
}
}
for (const FILE_REF &fref: wup->input_files) {
if (fref.optional) continue;
fip = fref.file_info;
if (fip->status != FILE_PRESENT) {
if (fipp) *fipp = fip;
ret = ERR_FILE_MISSING;
} else if (check_size) {
retval = fip->check_size();
if (retval) {
if (fipp) *fipp = fip;
ret = retval;
}
}
}
return ret;
}
// The app for the given result failed to start.
// Verify the app version files; maybe one of them was corrupted.
//
int CLIENT_STATE::verify_app_version_files(RESULT* rp) {
int ret = 0;
FILE_INFO* fip;
PROJECT* project = rp->project;
if (project->anonymous_platform) return 0;
APP_VERSION* avp = rp->avp;
for (FILE_REF &fref: avp->app_files) {
fip = fref.file_info;
int retval = fip->verify_file(true, true, false);
if (retval && log_flags.task_debug) {
msg_printf(fip->project, MSG_INFO,
"app version file %s: bad contents",
fip->name
);
ret = retval;
}
}
return ret;
}
inline double force_fraction(double f) {
if (f < 0) return 0;
if (f > 1) return 1;
return f;
}
double CLIENT_STATE::get_fraction_done(RESULT* result) {
ACTIVE_TASK* atp = active_tasks.lookup_result(result);
return atp ? force_fraction(atp->fraction_done) : 0.0;
}
// Find latest version of app for given platform
// or -1 if can't find one
//
int CLIENT_STATE::latest_version(APP* app, char* platform) {
int best = -1;
for (APP_VERSION* avp: app_versions) {
if (avp->app != app) continue;
if (strcmp(platform, avp->platform)) continue;
if (avp->version_num < best) continue;
best = avp->version_num;
}
return best;
}
// Find the ACTIVE_TASK in the current set with the matching PID
//
ACTIVE_TASK* ACTIVE_TASK_SET::lookup_pid(int pid) {
for (ACTIVE_TASK *atp: active_tasks) {
if (atp->pid == pid) return atp;
}
return NULL;
}
// Find the ACTIVE_TASK in the current set with the matching result
//
ACTIVE_TASK* ACTIVE_TASK_SET::lookup_result(RESULT* result) {
for (ACTIVE_TASK *atp: active_tasks) {
if (atp->result == result) {
return atp;
}
}
return NULL;
}
ACTIVE_TASK* ACTIVE_TASK_SET::lookup_slot(int slot) {
for (ACTIVE_TASK *atp: active_tasks) {
if (atp->slot == slot) {
return atp;
}
}
return NULL;
}
#ifndef SIM
// on startup, see if any active tasks have a finished file
// i.e. they finished as the client was shutting down
//
void ACTIVE_TASK_SET::check_for_finished_jobs() {
for (ACTIVE_TASK *atp: active_tasks) {
int exit_code;
if (atp->finish_file_present(exit_code)) {
msg_printf(atp->wup->project, MSG_INFO,
"Found finish file for %s; exit code %d",
atp->result->name, exit_code
);
atp->handle_exited_app(exit_code);
}
}
}
#endif
// check for overdue results once/day
// called at startup and once/sec after
//
void CLIENT_STATE::check_overdue() {
static double t = 0;
if (now < t) return;
active_tasks.report_overdue();
t = now + 86400;
}
////////////// DOCKER CLEANUP ///////////////////
// lists of image and container names for active jobs
//
struct DOCKER_JOB_INFO {
vector<string> images;
vector<string> containers;
bool image_present(string name) {
return std::find(images.begin(), images.end(), name) != images.end();
}
bool container_present(string name) {
return std::find(containers.begin(), containers.end(), name) != containers.end();
}
};
// clean up a Podman installation
// (Unix: the host; Win: a WSL distro)
//
void cleanup_docker(DOCKER_JOB_INFO &info, DOCKER_CONN &dc) {
int retval;
vector<string> out, out2;
char cmd[1024];
string name;
// debug
// dc.verbose = true;
// dc.command("system info; printenv", out);
// first containers
//
retval = dc.command("ps --all", out, false);
if (retval) {
fprintf(stderr, "Podman command failed: ps --all\n");
} else {
for (string line: out) {
retval = dc.parse_container_name(line, name);
if (retval) continue;
if (!docker_is_boinc_name(name.c_str())) continue;
if (info.container_present(name)) continue;
sprintf(cmd, "rm -f %s", name.c_str());
retval = dc.command(cmd, out2, true);
if (retval) {
fprintf(stderr, "Podman command failed: %s\n", cmd);
continue;
}
msg_printf(NULL, MSG_INFO,
"Removed unused Podman container: %s", name.c_str()
);
}
}
// then images
//
retval = dc.command("images", out, false);
if (retval) {
fprintf(stderr, "Podman command failed: images\n");
} else {
for (string line: out) {
retval = dc.parse_image_name(line, name);
if (retval) continue;
if (!docker_is_boinc_name(name.c_str())) continue;
if (info.image_present(name)) continue;
sprintf(cmd, "image rm %s", name.c_str());
retval = dc.command(cmd, out2, true);
if (retval) {
fprintf(stderr, "Podman command failed: %s\n", cmd);
continue;
}
msg_printf(NULL, MSG_INFO,
"Removed unused Podman image: %s", name.c_str()
);
}
}
}
// remove old BOINC images and containers from Podman installations
//
void CLIENT_STATE::docker_cleanup() {
// make lists of the images and containers used by active jobs
//
DOCKER_JOB_INFO info;
for (ACTIVE_TASK *atp: active_tasks.active_tasks) {
if (!strstr(atp->app_version->plan_class, "docker")) continue;
char buf[256];
escape_project_url(atp->wup->project->master_url, buf);
string s = docker_image_name(buf, atp->wup->name);
info.images.push_back(s);
s = docker_container_name(buf, atp->result->name);
info.containers.push_back(s);
}
// go through local Podman installations and remove
// BOINC images and containers not in the above lists
//
#ifdef _WIN32
for (WSL_DISTRO &wd: host_info.wsl_distros.distros) {
if (wd.docker_version.empty()) continue;
DOCKER_CONN dc;
dc.init(wd);
cleanup_docker(info, dc);
}
#else
if (strlen(host_info.docker_version)) {
DOCKER_CONN dc;
dc.init(host_info.docker_type);
cleanup_docker(info, dc);
}
#endif
}