|
1
|
/* |
|
2
|
** Copyright (c) 2018 D. Richard Hipp |
|
3
|
** |
|
4
|
** This program is free software; you can redistribute it and/or |
|
5
|
** modify it under the terms of the Simplified BSD License (also |
|
6
|
** known as the "2-Clause License" or "FreeBSD License".) |
|
7
|
** |
|
8
|
** This program is distributed in the hope that it will be useful, |
|
9
|
** but without any warranty; without even the implied warranty of |
|
10
|
** merchantability or fitness for a particular purpose. |
|
11
|
** |
|
12
|
** Author contact information: |
|
13
|
** [email protected] |
|
14
|
** http://www.hwaci.com/drh/ |
|
15
|
** |
|
16
|
******************************************************************************* |
|
17
|
** |
|
18
|
** This file contains code used to manage a background processes that |
|
19
|
** occur after user interaction with the repository. Examples of |
|
20
|
** backoffice processing includes: |
|
21
|
** |
|
22
|
** * Sending alerts and notifications |
|
23
|
** * Processing the email queue |
|
24
|
** * Handling post-receive hooks |
|
25
|
** * Automatically syncing to peer repositories |
|
26
|
** |
|
27
|
** Backoffice processing is automatically started whenever there are |
|
28
|
** changes to the repository. The backoffice process dies off after |
|
29
|
** a period of inactivity. |
|
30
|
** |
|
31
|
** Steps are taken to ensure that only a single backoffice process is |
|
32
|
** running at a time. Otherwise, there could be race conditions that |
|
33
|
** cause adverse effects such as multiple alerts for the same changes. |
|
34
|
** |
|
35
|
** At the same time, we do not want a backoffice process to run forever. |
|
36
|
** Backoffice processes should die off after doing whatever work they need |
|
37
|
** to do. In this way, we avoid having lots of idle processes in the |
|
38
|
** process table, doing nothing on rarely accessed repositories, and |
|
39
|
** if the Fossil binary is updated on a system, the backoffice processes |
|
40
|
** will restart using the new binary automatically. |
|
41
|
** |
|
42
|
** At any point in time there should be at most two backoffice processes. |
|
43
|
** There is a main process that is doing the actual work, and there is |
|
44
|
** a second stand-by process that is waiting for the main process to finish |
|
45
|
** and that will become the main process after a delay. |
|
46
|
** |
|
47
|
** After any successful web page reply, the backoffice_check_if_needed() |
|
48
|
** routine is called. That routine checks to see if both one or both of |
|
49
|
** the backoffice processes are already running. That routine remembers the |
|
50
|
** status in a global variable. |
|
51
|
** |
|
52
|
** Later, after the repository database is closed, the |
|
53
|
** backoffice_run_if_needed() routine is called. If the prior call |
|
54
|
** to backoffice_check_if_needed() indicated that backoffice processing |
|
55
|
** might be required, the run_if_needed() attempts to kick off a backoffice |
|
56
|
** process. |
|
57
|
** |
|
58
|
** All work performed by the backoffice is in the backoffice_work() |
|
59
|
** routine. |
|
60
|
*/ |
|
61
|
#if defined(_WIN32) |
|
62
|
# if defined(_WIN32_WINNT) |
|
63
|
# undef _WIN32_WINNT |
|
64
|
# endif |
|
65
|
# define _WIN32_WINNT 0x501 |
|
66
|
#endif |
|
67
|
#include "config.h" |
|
68
|
#include "backoffice.h" |
|
69
|
#include <time.h> |
|
70
|
#if defined(_WIN32) |
|
71
|
# include <windows.h> |
|
72
|
# include <stdio.h> |
|
73
|
# include <process.h> |
|
74
|
# if defined(__MINGW32__) |
|
75
|
# include <wchar.h> |
|
76
|
# endif |
|
77
|
# define GETPID (int)GetCurrentProcessId |
|
78
|
#else |
|
79
|
# include <unistd.h> |
|
80
|
# include <sys/types.h> |
|
81
|
# include <signal.h> |
|
82
|
# include <errno.h> |
|
83
|
# include <sys/time.h> |
|
84
|
# include <sys/resource.h> |
|
85
|
# include <fcntl.h> |
|
86
|
# define GETPID getpid |
|
87
|
#endif |
|
88
|
#include <time.h> |
|
89
|
|
|
90
|
/* |
|
91
|
** The BKOFCE_LEASE_TIME is the amount of time for which a single backoffice |
|
92
|
** processing run is valid. Each backoffice run monopolizes the lease for |
|
93
|
** at least this amount of time. Hopefully all backoffice processing is |
|
94
|
** finished much faster than this - usually in less than a second. But |
|
95
|
** regardless of how long each invocation lasts, successive backoffice runs |
|
96
|
** must be spaced out by at least this much time. |
|
97
|
*/ |
|
98
|
#define BKOFCE_LEASE_TIME 60 /* Length of lease validity in seconds */ |
|
99
|
|
|
100
|
#if LOCAL_INTERFACE |
|
101
|
/* |
|
102
|
** An instance of the following object describes a lease on the backoffice |
|
103
|
** processing timeslot. This lease is used to help ensure that no more than |
|
104
|
** one process is running backoffice at a time. |
|
105
|
*/ |
|
106
|
struct Lease { |
|
107
|
sqlite3_uint64 idCurrent; /* process ID for the current lease holder */ |
|
108
|
sqlite3_uint64 tmCurrent; /* Expiration of the current lease */ |
|
109
|
sqlite3_uint64 idNext; /* process ID for the next lease holder on queue */ |
|
110
|
sqlite3_uint64 tmNext; /* Expiration of the next lease */ |
|
111
|
}; |
|
112
|
#endif |
|
113
|
|
|
114
|
/*************************************************************************** |
|
115
|
** Local state variables |
|
116
|
** |
|
117
|
** Set to prevent backoffice processing from ever entering sleep or |
|
118
|
** otherwise taking a long time to complete. Set this when a user-visible |
|
119
|
** process might need to wait for backoffice to complete. |
|
120
|
*/ |
|
121
|
static int backofficeNoDelay = 0; |
|
122
|
|
|
123
|
/* This variable is set to the name of a database on which backoffice |
|
124
|
** should run if backoffice process is needed. It is set by the |
|
125
|
** backoffice_check_if_needed() routine which must be run while the database |
|
126
|
** file is open. Later, after the database is closed, the |
|
127
|
** backoffice_run_if_needed() will consult this variable to see if it |
|
128
|
** should be a no-op. |
|
129
|
** |
|
130
|
** The magic string "x" in this variable means "do not run the backoffice". |
|
131
|
*/ |
|
132
|
static char *backofficeDb = 0; |
|
133
|
|
|
134
|
/* |
|
135
|
** Log backoffice activity to a file named here. If not NULL, this |
|
136
|
** overrides the "backoffice-logfile" setting of the database. If NULL, |
|
137
|
** the "backoffice-logfile" setting is used instead. |
|
138
|
*/ |
|
139
|
static const char *backofficeLogfile = 0; |
|
140
|
|
|
141
|
/* |
|
142
|
** Write the log message into this open file. |
|
143
|
*/ |
|
144
|
static FILE *backofficeFILE = 0; |
|
145
|
|
|
146
|
/* |
|
147
|
** Write backoffice log messages on this BLOB. to this connection: |
|
148
|
*/ |
|
149
|
static Blob *backofficeBlob = 0; |
|
150
|
|
|
151
|
/* |
|
152
|
** Non-zero for extra logging detail. |
|
153
|
*/ |
|
154
|
static int backofficeLogDetail = 0; |
|
155
|
|
|
156
|
/* End of state variables |
|
157
|
****************************************************************************/ |
|
158
|
|
|
159
|
/* |
|
160
|
** This function emits a diagnostic message related to the processing in |
|
161
|
** this module. |
|
162
|
*/ |
|
163
|
#if defined(_WIN32) |
|
164
|
# define BKOFCE_ALWAYS_TRACE (1) |
|
165
|
extern void sqlite3_win32_write_debug(const char *, int); |
|
166
|
#else |
|
167
|
# define BKOFCE_ALWAYS_TRACE (0) |
|
168
|
#endif |
|
169
|
static void backofficeTrace(const char *zFormat, ...){ |
|
170
|
char *zMsg = 0; |
|
171
|
if( BKOFCE_ALWAYS_TRACE || g.fAnyTrace ){ |
|
172
|
va_list ap; |
|
173
|
va_start(ap, zFormat); |
|
174
|
zMsg = sqlite3_vmprintf(zFormat, ap); |
|
175
|
va_end(ap); |
|
176
|
#if defined(_WIN32) |
|
177
|
sqlite3_win32_write_debug(zMsg, -1); |
|
178
|
#endif |
|
179
|
} |
|
180
|
if( g.fAnyTrace ) fprintf(stderr, "%s", zMsg); |
|
181
|
if( zMsg ) sqlite3_free(zMsg); |
|
182
|
} |
|
183
|
|
|
184
|
/* |
|
185
|
** Do not allow backoffice processes to sleep waiting on a timeslot. |
|
186
|
** They must either do their work immediately or exit. |
|
187
|
** |
|
188
|
** In a perfect world, this interface would not exist, as there would |
|
189
|
** never be a problem with waiting backoffice threads. But in some cases |
|
190
|
** a backoffice will delay a UI thread, so we don't want them to run for |
|
191
|
** longer than needed. |
|
192
|
*/ |
|
193
|
void backoffice_no_delay(void){ |
|
194
|
backofficeNoDelay = 1; |
|
195
|
} |
|
196
|
|
|
197
|
/* |
|
198
|
** Sleeps for the specified number of milliseconds -OR- until interrupted |
|
199
|
** by another thread (if supported by the underlying platform). Non-zero |
|
200
|
** will be returned if the sleep was interrupted. |
|
201
|
*/ |
|
202
|
static int backofficeSleep(int milliseconds){ |
|
203
|
#if defined(_WIN32) |
|
204
|
assert( milliseconds>=0 ); |
|
205
|
if( SleepEx((DWORD)milliseconds, TRUE)==WAIT_IO_COMPLETION ){ |
|
206
|
return 1; |
|
207
|
} |
|
208
|
#else |
|
209
|
sqlite3_sleep(milliseconds); |
|
210
|
#endif |
|
211
|
return 0; |
|
212
|
} |
|
213
|
|
|
214
|
/* |
|
215
|
** Parse a unsigned 64-bit integer from a string. Return a pointer |
|
216
|
** to the character of z[] that occurs after the integer. |
|
217
|
*/ |
|
218
|
static const char *backofficeParseInt(const char *z, sqlite3_uint64 *pVal){ |
|
219
|
*pVal = 0; |
|
220
|
if( z==0 ) return 0; |
|
221
|
while( fossil_isspace(z[0]) ){ z++; } |
|
222
|
while( fossil_isdigit(z[0]) ){ |
|
223
|
*pVal = (*pVal)*10 + z[0] - '0'; |
|
224
|
z++; |
|
225
|
} |
|
226
|
return z; |
|
227
|
} |
|
228
|
|
|
229
|
/* |
|
230
|
** Read the "backoffice" property and parse it into a Lease object. |
|
231
|
** |
|
232
|
** The backoffice property should consist of four integers: |
|
233
|
** |
|
234
|
** (1) Process ID for the active backoffice process. |
|
235
|
** (2) Time (seconds since 1970) for when the active backoffice |
|
236
|
** lease expires. |
|
237
|
** (3) Process ID for the on-deck backoffice process. |
|
238
|
** (4) Time when the on-deck process should expire. |
|
239
|
** |
|
240
|
** No other process should start active backoffice processing until |
|
241
|
** process (1) no longer exists and the current time exceeds (2). |
|
242
|
*/ |
|
243
|
static void backofficeReadLease(Lease *pLease){ |
|
244
|
Stmt q; |
|
245
|
memset(pLease, 0, sizeof(*pLease)); |
|
246
|
db_unprotect(PROTECT_CONFIG); |
|
247
|
db_prepare(&q, "SELECT value FROM repository.config" |
|
248
|
" WHERE name='backoffice'"); |
|
249
|
if( db_step(&q)==SQLITE_ROW ){ |
|
250
|
const char *z = db_column_text(&q,0); |
|
251
|
z = backofficeParseInt(z, &pLease->idCurrent); |
|
252
|
z = backofficeParseInt(z, &pLease->tmCurrent); |
|
253
|
z = backofficeParseInt(z, &pLease->idNext); |
|
254
|
backofficeParseInt(z, &pLease->tmNext); |
|
255
|
} |
|
256
|
db_finalize(&q); |
|
257
|
db_protect_pop(); |
|
258
|
} |
|
259
|
|
|
260
|
/* |
|
261
|
** Return a string that describes how long it has been since the |
|
262
|
** last backoffice run. The string is obtained from fossil_malloc(). |
|
263
|
*/ |
|
264
|
char *backoffice_last_run(void){ |
|
265
|
Lease x; |
|
266
|
sqlite3_uint64 tmNow; |
|
267
|
double rAge; |
|
268
|
backofficeReadLease(&x); |
|
269
|
tmNow = time(0); |
|
270
|
if( x.tmCurrent==0 ){ |
|
271
|
return fossil_strdup("never"); |
|
272
|
} |
|
273
|
if( tmNow<=(x.tmCurrent-BKOFCE_LEASE_TIME) ){ |
|
274
|
return fossil_strdup("moments ago"); |
|
275
|
} |
|
276
|
rAge = (tmNow - (x.tmCurrent-BKOFCE_LEASE_TIME))/86400.0; |
|
277
|
return mprintf("%z ago", human_readable_age(rAge)); |
|
278
|
} |
|
279
|
|
|
280
|
/* |
|
281
|
** Write a lease to the backoffice property |
|
282
|
*/ |
|
283
|
static void backofficeWriteLease(Lease *pLease){ |
|
284
|
db_unprotect(PROTECT_CONFIG); |
|
285
|
db_multi_exec( |
|
286
|
"REPLACE INTO repository.config(name,value,mtime)" |
|
287
|
" VALUES('backoffice','%lld %lld %lld %lld',now())", |
|
288
|
pLease->idCurrent, pLease->tmCurrent, |
|
289
|
pLease->idNext, pLease->tmNext); |
|
290
|
db_protect_pop(); |
|
291
|
} |
|
292
|
|
|
293
|
/* |
|
294
|
** Check to see if the specified Win32 process is still alive. It |
|
295
|
** should be noted that even if this function returns non-zero, the |
|
296
|
** process may die before another operation on it can be completed. |
|
297
|
*/ |
|
298
|
#if defined(_WIN32) |
|
299
|
#ifndef PROCESS_QUERY_LIMITED_INFORMATION |
|
300
|
# define PROCESS_QUERY_LIMITED_INFORMATION (0x1000) |
|
301
|
#endif |
|
302
|
static int backofficeWin32ProcessExists(DWORD dwProcessId){ |
|
303
|
HANDLE hProcess; |
|
304
|
hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,FALSE,dwProcessId); |
|
305
|
if( hProcess==NULL ) return 0; |
|
306
|
CloseHandle(hProcess); |
|
307
|
return 1; |
|
308
|
} |
|
309
|
#endif |
|
310
|
|
|
311
|
/* |
|
312
|
** Check to see if the process identified by pid is alive. If |
|
313
|
** we cannot prove that the process is dead, return true. |
|
314
|
*/ |
|
315
|
static int backofficeProcessExists(sqlite3_uint64 pid){ |
|
316
|
#if defined(_WIN32) |
|
317
|
return pid>0 && backofficeWin32ProcessExists((DWORD)pid)!=0; |
|
318
|
#else |
|
319
|
return pid>0 && kill((pid_t)pid, 0)==0; |
|
320
|
#endif |
|
321
|
} |
|
322
|
|
|
323
|
/* |
|
324
|
** Check to see if the process identified by pid has finished. If |
|
325
|
** we cannot prove that the process is still running, return true. |
|
326
|
*/ |
|
327
|
static int backofficeProcessDone(sqlite3_uint64 pid){ |
|
328
|
#if defined(_WIN32) |
|
329
|
return pid<=0 || backofficeWin32ProcessExists((DWORD)pid)==0; |
|
330
|
#else |
|
331
|
return pid<=0 || kill((pid_t)pid, 0)!=0; |
|
332
|
#endif |
|
333
|
} |
|
334
|
|
|
335
|
/* |
|
336
|
** Return a process id number for the current process |
|
337
|
*/ |
|
338
|
static sqlite3_uint64 backofficeProcessId(void){ |
|
339
|
return (sqlite3_uint64)GETPID(); |
|
340
|
} |
|
341
|
|
|
342
|
|
|
343
|
/* |
|
344
|
** COMMAND: test-process-id |
|
345
|
** |
|
346
|
** Usage: %fossil [--sleep N] PROCESS-ID ... |
|
347
|
** |
|
348
|
** Show the current process id, and also tell whether or not all other |
|
349
|
** processes IDs on the command line are running or not. If the --sleep N |
|
350
|
** option is provide, then sleep for N seconds before exiting. |
|
351
|
*/ |
|
352
|
void test_process_id_command(void){ |
|
353
|
const char *zSleep = find_option("sleep",0,1); |
|
354
|
int i; |
|
355
|
verify_all_options(); |
|
356
|
fossil_print("ProcessID for this process: %lld\n", backofficeProcessId()); |
|
357
|
if( zSleep ) sqlite3_sleep(1000*atoi(zSleep)); |
|
358
|
for(i=2; i<g.argc; i++){ |
|
359
|
sqlite3_uint64 x = (sqlite3_uint64)atoi(g.argv[i]); |
|
360
|
fossil_print("ProcessId %lld: exists %d done %d\n", |
|
361
|
x, backofficeProcessExists(x), |
|
362
|
backofficeProcessDone(x)); |
|
363
|
} |
|
364
|
} |
|
365
|
|
|
366
|
/* |
|
367
|
** COMMAND: test-backoffice-lease |
|
368
|
** |
|
369
|
** Usage: %fossil test-backoffice-lease ?--reset? |
|
370
|
** |
|
371
|
** Print out information about the backoffice "lease" entry in the |
|
372
|
** config table that controls whether or not backoffice should run. |
|
373
|
** |
|
374
|
** If the --reset option is given, the backoffice lease is reset. |
|
375
|
** The use of the --reset option can be disruptive. It can cause two |
|
376
|
** or more backoffice processes to be run simultaneously. Use it with |
|
377
|
** caution. |
|
378
|
*/ |
|
379
|
void test_backoffice_lease(void){ |
|
380
|
sqlite3_int64 tmNow = time(0); |
|
381
|
Lease x; |
|
382
|
const char *zLease; |
|
383
|
db_find_and_open_repository(0,0); |
|
384
|
if( find_option("reset",0,0)!=0 ){ |
|
385
|
db_unprotect(PROTECT_CONFIG); |
|
386
|
db_multi_exec( |
|
387
|
"DELETE FROM repository.config WHERE name='backoffice'" |
|
388
|
); |
|
389
|
db_protect_pop(); |
|
390
|
} |
|
391
|
verify_all_options(); |
|
392
|
zLease = db_get("backoffice",""); |
|
393
|
fossil_print("now: %lld\n", tmNow); |
|
394
|
fossil_print("lease: \"%s\"\n", zLease); |
|
395
|
backofficeReadLease(&x); |
|
396
|
fossil_print("idCurrent: %-20lld", x.idCurrent); |
|
397
|
if( backofficeProcessExists(x.idCurrent) ) fossil_print(" (exists)"); |
|
398
|
if( backofficeProcessDone(x.idCurrent) ) fossil_print(" (done)"); |
|
399
|
fossil_print("\n"); |
|
400
|
fossil_print("tmCurrent: %-20lld", x.tmCurrent); |
|
401
|
if( x.tmCurrent>0 ){ |
|
402
|
fossil_print(" (now%+d)\n",x.tmCurrent-tmNow); |
|
403
|
}else{ |
|
404
|
fossil_print("\n"); |
|
405
|
} |
|
406
|
fossil_print("idNext: %-20lld", x.idNext); |
|
407
|
if( backofficeProcessExists(x.idNext) ) fossil_print(" (exists)"); |
|
408
|
if( backofficeProcessDone(x.idNext) ) fossil_print(" (done)"); |
|
409
|
fossil_print("\n"); |
|
410
|
fossil_print("tmNext: %-20lld", x.tmNext); |
|
411
|
if( x.tmNext>0 ){ |
|
412
|
fossil_print(" (now%+d)\n",x.tmNext-tmNow); |
|
413
|
}else{ |
|
414
|
fossil_print("\n"); |
|
415
|
} |
|
416
|
} |
|
417
|
|
|
418
|
/* |
|
419
|
** If backoffice processing is needed set the backofficeDb variable to the |
|
420
|
** name of the database file. If no backoffice processing is needed, |
|
421
|
** this routine makes no changes to state. |
|
422
|
*/ |
|
423
|
void backoffice_check_if_needed(void){ |
|
424
|
Lease x; |
|
425
|
sqlite3_uint64 tmNow; |
|
426
|
|
|
427
|
if( backofficeDb ) return; |
|
428
|
if( g.zRepositoryName==0 ) return; |
|
429
|
if( g.db==0 ) return; |
|
430
|
if( !db_table_exists("repository","config") ) return; |
|
431
|
if( db_get_boolean("backoffice-disable",0) ) return; |
|
432
|
tmNow = time(0); |
|
433
|
backofficeReadLease(&x); |
|
434
|
if( x.tmNext>=tmNow && backofficeProcessExists(x.idNext) ){ |
|
435
|
/* Another backoffice process is already queued up to run. This |
|
436
|
** process does not need to do any backoffice work. */ |
|
437
|
return; |
|
438
|
}else{ |
|
439
|
/* We need to run backup to be (at a minimum) on-deck */ |
|
440
|
backofficeDb = fossil_strdup(g.zRepositoryName); |
|
441
|
} |
|
442
|
} |
|
443
|
|
|
444
|
/* |
|
445
|
** Call this routine to disable backoffice |
|
446
|
*/ |
|
447
|
void backoffice_disable(void){ |
|
448
|
backofficeDb = "x"; |
|
449
|
} |
|
450
|
|
|
451
|
/* |
|
452
|
** Check for errors prior to running backoffice_thread() or backoffice_run(). |
|
453
|
*/ |
|
454
|
static void backoffice_error_check_one(int *pOnce){ |
|
455
|
if( *pOnce ){ |
|
456
|
fossil_panic("multiple calls to backoffice()"); |
|
457
|
} |
|
458
|
*pOnce = 1; |
|
459
|
if( g.db==0 ){ |
|
460
|
fossil_panic("database not open for backoffice processing"); |
|
461
|
} |
|
462
|
if( db_transaction_nesting_depth()!=0 ){ |
|
463
|
fossil_panic("transaction %s not closed prior to backoffice processing", |
|
464
|
db_transaction_start_point()); |
|
465
|
} |
|
466
|
} |
|
467
|
|
|
468
|
/* This is the main loop for backoffice processing. |
|
469
|
** |
|
470
|
** If another process is already working as the current backoffice and |
|
471
|
** the on-deck backoffice, then this routine returns very quickly |
|
472
|
** without doing any work. |
|
473
|
** |
|
474
|
** If no backoffice processes are running at all, this routine becomes |
|
475
|
** the main backoffice. |
|
476
|
** |
|
477
|
** If a primary backoffice is running, but an on-deck backoffice is |
|
478
|
** needed, this routine becomes that on-deck backoffice. |
|
479
|
*/ |
|
480
|
static void backoffice_thread(void){ |
|
481
|
Lease x; |
|
482
|
sqlite3_uint64 tmNow; |
|
483
|
sqlite3_uint64 idSelf; |
|
484
|
int lastWarning = 0; |
|
485
|
int warningDelay = 30; |
|
486
|
static int once = 0; |
|
487
|
|
|
488
|
if( sqlite3_db_readonly(g.db, 0) ) return; |
|
489
|
if( db_is_protected(PROTECT_READONLY) ) return; |
|
490
|
g.zPhase = "backoffice-pending"; |
|
491
|
backoffice_error_check_one(&once); |
|
492
|
idSelf = backofficeProcessId(); |
|
493
|
while(1){ |
|
494
|
tmNow = time(0); |
|
495
|
db_begin_write(); |
|
496
|
backofficeReadLease(&x); |
|
497
|
if( x.tmNext>=tmNow |
|
498
|
&& x.idNext!=idSelf |
|
499
|
&& backofficeProcessExists(x.idNext) |
|
500
|
){ |
|
501
|
/* Another backoffice process is already queued up to run. This |
|
502
|
** process does not need to do any backoffice work and can stop |
|
503
|
** immediately. */ |
|
504
|
db_end_transaction(0); |
|
505
|
backofficeTrace("/***** Backoffice Processing Not Needed In %d *****/\n", |
|
506
|
GETPID()); |
|
507
|
break; |
|
508
|
} |
|
509
|
if( x.tmCurrent<tmNow && backofficeProcessDone(x.idCurrent) ){ |
|
510
|
/* This process can start doing backoffice work immediately */ |
|
511
|
x.idCurrent = idSelf; |
|
512
|
x.tmCurrent = tmNow + BKOFCE_LEASE_TIME; |
|
513
|
x.idNext = 0; |
|
514
|
x.tmNext = 0; |
|
515
|
g.zPhase = "backoffice-work"; |
|
516
|
backofficeWriteLease(&x); |
|
517
|
db_end_transaction(0); |
|
518
|
backofficeTrace("/***** Begin Backoffice Processing %d *****/\n", |
|
519
|
GETPID()); |
|
520
|
backoffice_work(); |
|
521
|
break; |
|
522
|
} |
|
523
|
if( backofficeNoDelay || db_get_boolean("backoffice-nodelay",0) ){ |
|
524
|
/* If the no-delay flag is set, exit immediately rather than queuing |
|
525
|
** up. Assume that some future request will come along and handle any |
|
526
|
** necessary backoffice work. */ |
|
527
|
db_end_transaction(0); |
|
528
|
backofficeTrace( |
|
529
|
"/***** Backoffice No-Delay Exit For %d *****/\n", |
|
530
|
GETPID()); |
|
531
|
break; |
|
532
|
} |
|
533
|
/* This process needs to queue up and wait for the current lease |
|
534
|
** to expire before continuing. */ |
|
535
|
x.idNext = idSelf; |
|
536
|
x.tmNext = (tmNow>x.tmCurrent ? tmNow : x.tmCurrent) + BKOFCE_LEASE_TIME; |
|
537
|
backofficeWriteLease(&x); |
|
538
|
db_end_transaction(0); |
|
539
|
backofficeTrace("/***** Backoffice On-deck %d *****/\n", GETPID()); |
|
540
|
if( x.tmCurrent >= tmNow ){ |
|
541
|
if( backofficeSleep(1000*(x.tmCurrent - tmNow + 1)) ){ |
|
542
|
/* The sleep was interrupted by a signal from another thread. */ |
|
543
|
backofficeTrace("/***** Backoffice Interrupt %d *****/\n", GETPID()); |
|
544
|
db_end_transaction(0); |
|
545
|
break; |
|
546
|
} |
|
547
|
}else{ |
|
548
|
if( (sqlite3_uint64)(lastWarning+warningDelay) < tmNow ){ |
|
549
|
sqlite3_int64 runningFor = BKOFCE_LEASE_TIME + tmNow - x.tmCurrent; |
|
550
|
if( warningDelay>=240 && runningFor<1800 ){ |
|
551
|
fossil_warning( |
|
552
|
"backoffice process %lld still running after %d seconds", |
|
553
|
x.idCurrent, runningFor); |
|
554
|
} |
|
555
|
lastWarning = tmNow; |
|
556
|
warningDelay *= 2; |
|
557
|
} |
|
558
|
if( backofficeSleep(1000) ){ |
|
559
|
/* The sleep was interrupted by a signal from another thread. */ |
|
560
|
backofficeTrace("/***** Backoffice Interrupt %d *****/\n", GETPID()); |
|
561
|
db_end_transaction(0); |
|
562
|
break; |
|
563
|
} |
|
564
|
} |
|
565
|
} |
|
566
|
return; |
|
567
|
} |
|
568
|
|
|
569
|
/* |
|
570
|
** Append to a message to the backoffice log, if the log is open. |
|
571
|
*/ |
|
572
|
void backoffice_log(const char *zFormat, ...){ |
|
573
|
va_list ap; |
|
574
|
if( backofficeBlob==0 ) return; |
|
575
|
blob_append_char(backofficeBlob, ' '); |
|
576
|
va_start(ap, zFormat); |
|
577
|
blob_vappendf(backofficeBlob, zFormat, ap); |
|
578
|
va_end(ap); |
|
579
|
} |
|
580
|
|
|
581
|
#if !defined(_WIN32) |
|
582
|
/* |
|
583
|
** Capture routine for signals while running backoffice. |
|
584
|
*/ |
|
585
|
static void backoffice_signal_handler(int sig){ |
|
586
|
const char *zSig = 0; |
|
587
|
if( sig==SIGSEGV ) zSig = "SIGSEGV"; |
|
588
|
if( sig==SIGFPE ) zSig = "SIGFPE"; |
|
589
|
if( sig==SIGABRT ) zSig = "SIGABRT"; |
|
590
|
if( sig==SIGILL ) zSig = "SIGILL"; |
|
591
|
if( zSig==0 ){ |
|
592
|
backoffice_log("signal-%d", sig); |
|
593
|
}else{ |
|
594
|
backoffice_log("%s", zSig); |
|
595
|
} |
|
596
|
fprintf(backofficeFILE, "%s\n", blob_str(backofficeBlob)); |
|
597
|
fflush(backofficeFILE); |
|
598
|
exit(1); |
|
599
|
} |
|
600
|
#endif |
|
601
|
|
|
602
|
#if !defined(_WIN32) |
|
603
|
/* |
|
604
|
** Convert a struct timeval into an integer number of microseconds |
|
605
|
*/ |
|
606
|
static long long int tvms(struct timeval *p){ |
|
607
|
return ((long long int)p->tv_sec)*1000000 + (long long int)p->tv_usec; |
|
608
|
} |
|
609
|
#endif |
|
610
|
|
|
611
|
|
|
612
|
/* |
|
613
|
** This routine runs to do the backoffice processing. When adding new |
|
614
|
** backoffice processing tasks, add them here. |
|
615
|
*/ |
|
616
|
void backoffice_work(void){ |
|
617
|
/* Log the backoffice run for testing purposes. For production deployments |
|
618
|
** the "backoffice-logfile" property should be unset and the following code |
|
619
|
** should be a no-op. */ |
|
620
|
const char *zLog = backofficeLogfile; |
|
621
|
Blob log; |
|
622
|
int nThis; |
|
623
|
int nTotal = 0; |
|
624
|
#if !defined(_WIN32) |
|
625
|
struct timeval sStart, sEnd; |
|
626
|
#endif |
|
627
|
if( zLog==0 ) zLog = db_get("backoffice-logfile",0); |
|
628
|
if( zLog && zLog[0] && (backofficeFILE = fossil_fopen(zLog,"a"))!=0 ){ |
|
629
|
int i; |
|
630
|
char *zName = db_get("project-name",0); |
|
631
|
#if !defined(_WIN32) |
|
632
|
gettimeofday(&sStart, 0); |
|
633
|
signal(SIGSEGV, backoffice_signal_handler); |
|
634
|
signal(SIGABRT, backoffice_signal_handler); |
|
635
|
signal(SIGFPE, backoffice_signal_handler); |
|
636
|
signal(SIGILL, backoffice_signal_handler); |
|
637
|
#endif |
|
638
|
if( zName==0 ){ |
|
639
|
zName = (char*)file_tail(g.zRepositoryName); |
|
640
|
if( zName==0 ) zName = "(unnamed)"; |
|
641
|
}else{ |
|
642
|
/* Convert all spaces in the "project-name" into dashes */ |
|
643
|
for(i=0; zName[i]; i++){ if( zName[i]==' ' ) zName[i] = '-'; } |
|
644
|
} |
|
645
|
blob_init(&log, 0, 0); |
|
646
|
backofficeBlob = &log; |
|
647
|
blob_appendf(&log, "%s %s", db_text(0, "SELECT datetime('now')"), zName); |
|
648
|
} |
|
649
|
|
|
650
|
/* Here is where the actual work of the backoffice happens */ |
|
651
|
g.zPhase = "backoffice-alerts"; |
|
652
|
nThis = alert_backoffice(0); |
|
653
|
if( nThis ){ backoffice_log("%d alerts", nThis); nTotal += nThis; } |
|
654
|
g.zPhase = "backoffice-hooks"; |
|
655
|
nThis = hook_backoffice(); |
|
656
|
if( nThis ){ backoffice_log("%d hooks", nThis); nTotal += nThis; } |
|
657
|
g.zPhase = "backoffice-close"; |
|
658
|
|
|
659
|
/* Close the log */ |
|
660
|
if( backofficeFILE ){ |
|
661
|
if( nTotal || backofficeLogDetail ){ |
|
662
|
if( nTotal==0 ) backoffice_log("no-op"); |
|
663
|
#if !defined(_WIN32) |
|
664
|
gettimeofday(&sEnd,0); |
|
665
|
backoffice_log("elapse-time %d us", tvms(&sEnd) - tvms(&sStart)); |
|
666
|
#endif |
|
667
|
fprintf(backofficeFILE, "%s\n", blob_str(backofficeBlob)); |
|
668
|
} |
|
669
|
fclose(backofficeFILE); |
|
670
|
} |
|
671
|
} |
|
672
|
|
|
673
|
/* |
|
674
|
** COMMAND: backoffice* |
|
675
|
** |
|
676
|
** Usage: %fossil backoffice [OPTIONS...] [REPOSITORIES...] |
|
677
|
** |
|
678
|
** Run backoffice processing on the repositories listed. If no |
|
679
|
** repository is specified, run it on the repository of the local check-out. |
|
680
|
** |
|
681
|
** This might be done by a cron job or similar to make sure backoffice |
|
682
|
** processing happens periodically. Or, the --poll option can be used |
|
683
|
** to run this command as a daemon that will periodically invoke backoffice |
|
684
|
** on a collection of repositories. |
|
685
|
** |
|
686
|
** If only a single repository is named and --poll is omitted, then the |
|
687
|
** backoffice work is done in-process. But if there are multiple repositories |
|
688
|
** or if --poll is used, a separate sub-process is started for each poll of |
|
689
|
** each repository. |
|
690
|
** |
|
691
|
** Standard options: |
|
692
|
** |
|
693
|
** --debug Show what this command is doing |
|
694
|
** |
|
695
|
** --logfile FILE Append a log of backoffice actions onto FILE |
|
696
|
** |
|
697
|
** --min N When polling, invoke backoffice at least |
|
698
|
** once every N seconds even if the repository |
|
699
|
** never changes. 0 or negative means disable |
|
700
|
** this feature. Default: 3600 (once per hour). |
|
701
|
** |
|
702
|
** --poll N Repeat backoffice calls for repositories that |
|
703
|
** change in approximately N-second intervals. |
|
704
|
** N less than 1 turns polling off (the default). |
|
705
|
** Recommended polling interval: 60 seconds. |
|
706
|
** |
|
707
|
** --trace Enable debugging output on stderr |
|
708
|
** |
|
709
|
** Options intended for internal use only which may change or be |
|
710
|
** discontinued in future releases: |
|
711
|
** |
|
712
|
** --nodelay Do not queue up or wait for a backoffice job |
|
713
|
** to complete. If no work is available or if |
|
714
|
** backoffice has run recently, return immediately. |
|
715
|
** |
|
716
|
** --nolease Always run backoffice, even if there is a lease |
|
717
|
** conflict. This option implies --nodelay. This |
|
718
|
** option is added to secondary backoffice commands |
|
719
|
** that are invoked by the --poll option. |
|
720
|
*/ |
|
721
|
void backoffice_command(void){ |
|
722
|
int nPoll; |
|
723
|
int nMin; |
|
724
|
const char *zPoll; |
|
725
|
int bDebug = 0; |
|
726
|
int bNoLease = 0; |
|
727
|
unsigned int nCmd = 0; |
|
728
|
if( find_option("trace",0,0)!=0 ) g.fAnyTrace = 1; |
|
729
|
if( find_option("nodelay",0,0)!=0 ) backofficeNoDelay = 1; |
|
730
|
backofficeLogfile = find_option("logfile",0,1); |
|
731
|
zPoll = find_option("poll",0,1); |
|
732
|
nPoll = zPoll ? atoi(zPoll) : 0; |
|
733
|
zPoll = find_option("min",0,1); |
|
734
|
nMin = zPoll ? atoi(zPoll) : 3600; |
|
735
|
bDebug = find_option("debug",0,0)!=0; |
|
736
|
bNoLease = find_option("nolease",0,0)!=0; |
|
737
|
|
|
738
|
/* Silently consume the -R or --repository flag, leaving behind its |
|
739
|
** argument. This is for legacy compatibility. Older versions of the |
|
740
|
** backoffice command only ran on a single repository that was specified |
|
741
|
** using the -R option. */ |
|
742
|
(void)find_option("repository","R",0); |
|
743
|
|
|
744
|
verify_all_options(); |
|
745
|
if( g.argc>3 || nPoll>0 ){ |
|
746
|
/* Either there are multiple repositories named on the command-line |
|
747
|
** or we are polling. In either case, each backoffice should be run |
|
748
|
** using a separate sub-process */ |
|
749
|
int i; |
|
750
|
time_t iNow = 0; |
|
751
|
time_t ix; |
|
752
|
i64 *aLastRun = fossil_malloc( sizeof(i64)*g.argc ); |
|
753
|
memset(aLastRun, 0, sizeof(i64)*g.argc ); |
|
754
|
while( 1 /* exit via "break;" */){ |
|
755
|
time_t iNext = time(0); |
|
756
|
for(i=2; i<g.argc; i++){ |
|
757
|
Blob cmd; |
|
758
|
if( !file_isfile(g.argv[i], ExtFILE) ){ |
|
759
|
continue; /* Repo no longer exists. Ignore it. */ |
|
760
|
} |
|
761
|
if( iNow |
|
762
|
&& iNow>file_mtime(g.argv[i], ExtFILE) |
|
763
|
&& (nMin<=0 || aLastRun[i]+nMin>iNow) |
|
764
|
){ |
|
765
|
continue; /* Not yet time to run this one */ |
|
766
|
} |
|
767
|
blob_init(&cmd, 0, 0); |
|
768
|
blob_append_escaped_arg(&cmd, g.nameOfExe, 1); |
|
769
|
blob_append(&cmd, " backoffice --nodelay", -1); |
|
770
|
if( g.fAnyTrace ){ |
|
771
|
blob_append(&cmd, " --trace", -1); |
|
772
|
} |
|
773
|
if( bDebug ){ |
|
774
|
blob_append(&cmd, " --debug", -1); |
|
775
|
} |
|
776
|
if( nPoll>0 ){ |
|
777
|
blob_append(&cmd, " --nolease", -1); |
|
778
|
} |
|
779
|
if( backofficeLogfile ){ |
|
780
|
blob_append(&cmd, " --logfile", -1); |
|
781
|
blob_append_escaped_arg(&cmd, backofficeLogfile, 1); |
|
782
|
} |
|
783
|
blob_append_escaped_arg(&cmd, g.argv[i], 1); |
|
784
|
nCmd++; |
|
785
|
if( bDebug ){ |
|
786
|
fossil_print("COMMAND[%u]: %s\n", nCmd, blob_str(&cmd)); |
|
787
|
} |
|
788
|
fossil_system(blob_str(&cmd)); |
|
789
|
aLastRun[i] = iNext; |
|
790
|
blob_reset(&cmd); |
|
791
|
} |
|
792
|
if( nPoll<1 ) break; |
|
793
|
iNow = iNext; |
|
794
|
ix = time(0); |
|
795
|
if( ix < iNow+nPoll ){ |
|
796
|
sqlite3_int64 nMS = (iNow + nPoll - ix)*1000; |
|
797
|
if( bDebug )fossil_print("SLEEP: %lld\n", nMS); |
|
798
|
sqlite3_sleep((int)nMS); |
|
799
|
} |
|
800
|
} |
|
801
|
}else{ |
|
802
|
/* Not polling and only one repository named. Backoffice is run |
|
803
|
** once by this process, which then exits */ |
|
804
|
if( g.argc==3 ){ |
|
805
|
g.zRepositoryOption = g.argv[2]; |
|
806
|
g.argc--; |
|
807
|
} |
|
808
|
db_find_and_open_repository(0,0); |
|
809
|
if( bDebug ){ |
|
810
|
backofficeLogDetail = 1; |
|
811
|
} |
|
812
|
if( bNoLease ){ |
|
813
|
backoffice_work(); |
|
814
|
}else{ |
|
815
|
backoffice_thread(); |
|
816
|
} |
|
817
|
} |
|
818
|
} |
|
819
|
|
|
820
|
/* |
|
821
|
** This is the main interface to backoffice from the rest of the system. |
|
822
|
** This routine launches either backoffice_thread() directly or as a |
|
823
|
** subprocess. |
|
824
|
*/ |
|
825
|
void backoffice_run_if_needed(void){ |
|
826
|
if( backofficeDb==0 ) return; |
|
827
|
if( strcmp(backofficeDb,"x")==0 ) return; |
|
828
|
if( g.db ) return; |
|
829
|
if( g.repositoryOpen ) return; |
|
830
|
#if defined(_WIN32) |
|
831
|
{ |
|
832
|
int i; |
|
833
|
intptr_t x; |
|
834
|
char *argv[4]; |
|
835
|
wchar_t *ax[5]; |
|
836
|
argv[0] = g.nameOfExe; |
|
837
|
argv[1] = "backoffice"; |
|
838
|
argv[2] = "-R"; |
|
839
|
argv[3] = backofficeDb; |
|
840
|
ax[4] = 0; |
|
841
|
for(i=0; i<=3; i++) ax[i] = fossil_utf8_to_unicode(argv[i]); |
|
842
|
x = _wspawnv(_P_NOWAIT, ax[0], (const wchar_t * const *)ax); |
|
843
|
for(i=0; i<=3; i++) fossil_unicode_free(ax[i]); |
|
844
|
backofficeTrace( |
|
845
|
"/***** Subprocess %d creates backoffice child %lu *****/\n", |
|
846
|
GETPID(), GetProcessId((HANDLE)x)); |
|
847
|
if( x>=0 ) return; |
|
848
|
} |
|
849
|
#else /* unix */ |
|
850
|
{ |
|
851
|
pid_t pid = fork(); |
|
852
|
if( pid>0 ){ |
|
853
|
/* This is the parent in a successful fork(). Return immediately. */ |
|
854
|
backofficeTrace( |
|
855
|
"/***** Subprocess %d creates backoffice child %d *****/\n", |
|
856
|
GETPID(), (int)pid); |
|
857
|
return; |
|
858
|
} |
|
859
|
if( pid==0 ){ |
|
860
|
/* This is the child of a successful fork(). Run backoffice. */ |
|
861
|
int i; |
|
862
|
setsid(); |
|
863
|
for(i=0; i<=2; i++){ |
|
864
|
close(i); |
|
865
|
open("/dev/null", O_RDWR); |
|
866
|
} |
|
867
|
for(i=3; i<100; i++){ close(i); } |
|
868
|
g.fDebug = 0; |
|
869
|
g.httpIn = 0; |
|
870
|
g.httpOut = 0; |
|
871
|
db_open_repository(backofficeDb); |
|
872
|
backofficeDb = "x"; |
|
873
|
backoffice_thread(); |
|
874
|
db_close(1); |
|
875
|
backofficeTrace("/***** Backoffice Child %d exits *****/\n", GETPID()); |
|
876
|
exit(0); |
|
877
|
} |
|
878
|
fossil_warning("backoffice process %d fork failed, errno %d", GETPID(), |
|
879
|
errno); |
|
880
|
} |
|
881
|
#endif |
|
882
|
/* Fork() failed or is unavailable. Run backoffice in this process, but |
|
883
|
** do so with the no-delay setting. |
|
884
|
*/ |
|
885
|
backofficeNoDelay = 1; |
|
886
|
db_open_repository(backofficeDb); |
|
887
|
backofficeDb = "x"; |
|
888
|
backoffice_thread(); |
|
889
|
db_close(1); |
|
890
|
} |
|
891
|
|