fileMonitor.C 17.8 KB
Newer Older
1
2
3
4
/*---------------------------------------------------------------------------*\
  =========                 |
  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
   \\    /   O peration     |
5
    \\  /    A nd           | Copyright (C) 2018 OpenCFD Ltd.
6
     \\/     M anipulation  |
OpenFOAM bot's avatar
OpenFOAM bot committed
7
8
-------------------------------------------------------------------------------
                            | Copyright (C) 2011-2016 OpenFOAM Foundation
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
-------------------------------------------------------------------------------
License
    This file is part of OpenFOAM.

    OpenFOAM is free software: you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    OpenFOAM 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 General Public License
    for more details.

    You should have received a copy of the GNU General Public License
    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.

26
\*---------------------------------------------------------------------------*/
27
28
29
30
31

#include "fileMonitor.H"
#include "IOstreams.H"
#include "Pstream.H"
#include "PackedList.H"
32
#include "PstreamReduceOps.H"
33
#include "OSspecific.H"
34
#include "regIOobject.H"     // for fileModificationSkew symbol
35

36
#ifdef FOAM_USE_INOTIFY
37
38
39
40
41
42
43
    #include <unistd.h>
    #include <sys/inotify.h>
    #include <sys/ioctl.h>
    #include <errno.h>
    #define EVENT_SIZE  ( sizeof (struct inotify_event) )
    #define EVENT_LEN   (EVENT_SIZE + 16)
    #define EVENT_BUF_LEN     ( 1024 * EVENT_LEN )
44
45
46
47
#endif

// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //

48
49
50
51
52
const Foam::Enum
<
    Foam::fileMonitor::fileState
>
Foam::fileMonitor::fileStateNames_
Mark Olesen's avatar
Mark Olesen committed
53
({
54
55
56
    { fileState::UNMODIFIED, "unmodified" },
    { fileState::MODIFIED, "modified" },
    { fileState::DELETED, "deleted" },
Mark Olesen's avatar
Mark Olesen committed
57
});
58

59
60
61

namespace Foam
{
62
63
    defineTypeNameAndDebug(fileMonitor, 0);

64
    //- Reduction operator for PackedList of fileState
65
    class reduceFileStates
66
67
    {
        public:
68
69
        unsigned int operator()(const unsigned int x, const unsigned int y)
        const
70
        {
71
            // x,y are sets of 2bits representing fileState
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90

            unsigned int mask = 3u;
            unsigned int shift = 0;
            unsigned int result = 0;

            while (mask)
            {
                // Combine state
                unsigned int xState = (x & mask) >> shift;
                unsigned int yState = (y & mask) >> shift;

                // Combine and add to result. Combine is such that UNMODIFIED
                // wins.
                unsigned int state = min(xState, yState);
                result |= (state << shift);

                shift += 2;
                mask <<= 2;
            }
91
92
93
94
            return result;
        }
    };

95
    //- Combine operator for PackedList of fileState
96
97
98
99
100
101
    class combineReduceFileStates
    {
        public:
        void operator()(unsigned int& x, const unsigned int y) const
        {
            x = reduceFileStates()(x, y);
102
103
        }
    };
104
105
106



107
    //-  Internal tracking via stat(3p) or inotify(7)
108
109
110
111
    class fileMonitorWatcher
    {
    public:

112
        const bool useInotify_;
113

114
        // For inotify
115

116
117
            //- File descriptor for the inotify instance
            int inotifyFd_;
118

119
120
121
            //- Current watchIDs and corresponding directory id
            DynamicList<label> dirWatches_;
            DynamicList<fileName> dirFiles_;
122

123
124
125
        // For stat

            //- From watch descriptor to modified time
126
            DynamicList<double> lastMod_;
127
128


129

130
        //- Initialise inotify
131
        inline fileMonitorWatcher(const bool useInotify, const label sz = 20)
132
        :
133
134
            useInotify_(useInotify),
            inotifyFd_(-1)
135
        {
136
            if (useInotify_)
137
            {
138
                #ifdef FOAM_USE_INOTIFY
139
140
141
142
143
                inotifyFd_ = inotify_init();
                dirWatches_.setCapacity(sz);
                dirFiles_.setCapacity(sz);

                if (inotifyFd_ < 0)
144
                {
145
146
147
148
                    static bool hasWarned = false;
                    if (!hasWarned)
                    {
                        hasWarned = true;
149
                        WarningInFunction
150
151
152
153
154
155
156
157
158
159
160
161
162
163
                            << "Failed allocating an inotify descriptor : "
                            << string(strerror(errno)) << endl
                            << "    Please increase the number of allowable "
                            << "inotify instances" << endl
                            << "    (/proc/sys/fs/inotify/max_user_instances"
                            << " on Linux)" << endl
                            << "    , switch off runTimeModifiable." << endl
                            << "    or compile this file without "
                            << "FOAM_USE_INOTIFY"
                            << " to use time stamps instead of inotify." << endl
                            << "    Continuing without additional file"
                            << " monitoring."
                            << endl;
                    }
164
                }
165
                #else
166
                    FatalErrorInFunction
167
168
                        << "You selected inotify but this file was compiled"
                        << " without FOAM_USE_INOTIFY"
mattijs's avatar
mattijs committed
169
                        << " Please select another fileModification test method"
170
                        << exit(FatalError);
171
                #endif
172
173
174
175
            }
            else
            {
                lastMod_.setCapacity(sz);
176
177
            }
        }
178

179
        //- Remove all watches
180
181
        inline ~fileMonitorWatcher()
        {
182
            #ifdef FOAM_USE_INOTIFY
183
            if (useInotify_ && inotifyFd_ >= 0)
184
            {
185
                forAll(dirWatches_, i)
186
                {
187
                    if (dirWatches_[i] >= 0)
188
                    {
189
190
                        if (inotify_rm_watch(inotifyFd_, int(dirWatches_[i])))
                        {
191
                            WarningInFunction
192
193
194
                                << "Failed deleting directory watch "
                                << dirWatches_[i] << endl;
                        }
195
196
197
                    }
                }
            }
198
            #endif
199
200
201
        }

        inline bool addWatch(const label watchFd, const fileName& fName)
202
        {
203
            if (useInotify_)
204
            {
205
206
207
208
                if (inotifyFd_ < 0)
                {
                    return false;
                }
209

210
                #ifdef FOAM_USE_INOTIFY
211
212
213
                // Add/retrieve watch on directory containing file.
                // Note that fName might be non-existing in special situations
                // (master-only reading for IODictionaries)
214

215
216
217
218
219
220
221
222
223
224
225
226
227
228
                const fileName dir = fName.path();

                label dirWatchID = -1;
                if (isDir(dir))
                {
                    dirWatchID = inotify_add_watch
                    (
                        inotifyFd_,
                        dir.c_str(),
                        IN_CLOSE_WRITE
                    );

                    if (dirWatchID < 0)
                    {
229
                        FatalErrorInFunction
230
231
232
233
234
235
236
237
238
239
                            << "Failed adding watch " << watchFd
                            << " to directory " << fName << " due to "
                            << string(strerror(errno))
                            << exit(FatalError);
                    }
                }

                if (watchFd < dirWatches_.size() && dirWatches_[watchFd] != -1)
                {
                    // Reuse of watchFd : should have dir watchID set to -1.
240
                    FatalErrorInFunction
241
242
243
244
                        << "Problem adding watch " << watchFd
                        << " to file " << fName
                        << abort(FatalError);
                }
245

246
247
                dirWatches_(watchFd) = dirWatchID;
                dirFiles_(watchFd) = fName.name();
248
                #endif
249
250
            }
            else
251
            {
252
253
254
                if (watchFd < lastMod_.size() && lastMod_[watchFd] != 0)
                {
                    // Reuse of watchFd : should have lastMod set to 0.
255
                    FatalErrorInFunction
256
257
258
259
260
                        << "Problem adding watch " << watchFd
                        << " to file " << fName
                        << abort(FatalError);
                }

261
                lastMod_(watchFd) = highResLastModified(fName);
262
263
264
            }

            return true;
265
266
267
268
        }

        inline bool removeWatch(const label watchFd)
        {
269
            if (useInotify_)
270
            {
271
272
273
274
                if (inotifyFd_ < 0)
                {
                    return false;
                }
275

276
277
278
279
280
281
                dirWatches_[watchFd] = -1;
            }
            else
            {
                lastMod_[watchFd] = 0;
            }
282
            return true;
283
284
285
        }

    };
286
287
288
289
290
291
292
}


// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //

void Foam::fileMonitor::checkFiles() const
{
293
    if (useInotify_)
294
    {
295
        #ifdef FOAM_USE_INOTIFY
296
297
        // Large buffer for lots of events
        char buffer[EVENT_BUF_LEN];
298

299
        while (true)
300
        {
301
302
303
304
305
306
307
308
309
310
311
312
            struct timeval zeroTimeout = {0, 0};

            //- Pre-allocated structure containing file descriptors
            fd_set fdSet;
            // Add notify descriptor to select fd_set
            FD_ZERO(&fdSet);
            FD_SET(watcher_->inotifyFd_, &fdSet);

            int ready = select
            (
                watcher_->inotifyFd_+1,     // num filedescriptors in fdSet
                &fdSet,             // fdSet with only inotifyFd
313
314
                nullptr,               // No writefds
                nullptr,               // No errorfds
315
316
                &zeroTimeout        // eNo timeout
            );
317

318
            if (ready < 0)
319
            {
320
                FatalErrorInFunction
321
322
                    << "Problem in issuing select."
                    << abort(FatalError);
323
            }
324
            else if (FD_ISSET(watcher_->inotifyFd_, &fdSet))
325
            {
326
                // Read events
327
                ssize_t nBytes = ::read
328
329
330
331
332
333
334
                (
                    watcher_->inotifyFd_,
                    buffer,
                    EVENT_BUF_LEN
                );

                if (nBytes < 0)
335
                {
336
                    FatalErrorInFunction
337
338
339
                        << "read of " << watcher_->inotifyFd_
                        << " failed with " << label(nBytes)
                        << abort(FatalError);
340
                }
341
342
343
344

                // Go through buffer, consuming events
                int i = 0;
                while (i < nBytes)
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
                    const struct inotify_event* inotifyEvent =
                        reinterpret_cast<const struct inotify_event*>
                        (
                            &buffer[i]
                        );

                    //Pout<< "watchFd:" << inotifyEvent->wd << nl
                    //    << "mask:" << inotifyEvent->mask << nl
                    //  << endl;
                    //Pout<< "file:" << fileName(inotifyEvent->name) << endl;
                    //Pout<< "len:" << inotifyEvent->len << endl;

                    if
                    (
                        (inotifyEvent->mask & IN_CLOSE_WRITE)
                     && inotifyEvent->len
                    )
                    {
                        // Search for file
                        forAll(watcher_->dirWatches_, i)
                        {
                            label id = watcher_->dirWatches_[i];
                            if
                            (
                                id == inotifyEvent->wd
                             && inotifyEvent->name == watcher_->dirFiles_[i]
                            )
                            {
                                // Correct directory and name
mattijs's avatar
mattijs committed
375
                                localState_[i] = MODIFIED;
376
377
378
379
380
                            }
                        }
                    }

                    i += EVENT_SIZE + inotifyEvent->len;
381
                }
382
            }
383
384
385
386
387
            else
            {
                // No data
                return;
            }
388
        }
389
        #endif
390
    }
391
    else
392
    {
393
        forAll(watcher_->lastMod_, watchFd)
394
        {
395
            double oldTime = watcher_->lastMod_[watchFd];
396

397
            if (oldTime != 0)
398
            {
399
                const fileName& fName = watchFile_[watchFd];
400
                double newTime = highResLastModified(fName);
mattijs's avatar
mattijs committed
401

402
                if (newTime == 0)
mattijs's avatar
mattijs committed
403
                {
mattijs's avatar
mattijs committed
404
                    localState_[watchFd] = DELETED;
405
406
407
408
                }
                else
                {
                    if (newTime > (oldTime + regIOobject::fileModificationSkew))
409
                    {
mattijs's avatar
mattijs committed
410
                        localState_[watchFd] = MODIFIED;
411
412
413
                    }
                    else
                    {
mattijs's avatar
mattijs committed
414
                        localState_[watchFd] = UNMODIFIED;
415
                    }
mattijs's avatar
mattijs committed
416
                }
417
418
419
            }
        }
    }
420
}
421

mattijs's avatar
mattijs committed
422

423
424
425
// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //


426
Foam::fileMonitor::fileMonitor(const bool useInotify)
427
:
428
    useInotify_(useInotify),
mattijs's avatar
mattijs committed
429
    localState_(20),
430
431
    state_(20),
    watchFile_(20),
432
    freeWatchFds_(2),
433
    watcher_(new fileMonitorWatcher(useInotify_, 20))
434
435
436
437
438
439
{}


// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //

Foam::fileMonitor::~fileMonitor()
440
{}
441
442
443
444


// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //

445
446
// Note: fName might not exist (on slaves if in master-only mode for
// regIOobject)
447
448
Foam::label Foam::fileMonitor::addWatch(const fileName& fName)
{
449
450
451
452
    if (debug)
    {
        Pout<< "fileMonitor : adding watch on file " << fName << endl;
    }
453

454
    label watchFd;
455

456
    if (freeWatchFds_.size())
457
    {
458
        watchFd = freeWatchFds_.remove();
459
460
461
462
463
464
465
    }
    else
    {
        watchFd = state_.size();
    }

    watcher_->addWatch(watchFd, fName);
466
467
468
469
470
471
472
473
474

    if (debug)
    {
        Pout<< "fileMonitor : added watch " << watchFd << " on file "
            << fName << endl;
    }

    if (watchFd < 0)
    {
475
        WarningInFunction
476
477
478
479
            << "could not add watch for file " << fName << endl;
    }
    else
    {
mattijs's avatar
mattijs committed
480
        localState_(watchFd) = UNMODIFIED;
481
482
        state_(watchFd) = UNMODIFIED;
        watchFile_(watchFd) = fName;
483
484
485
486
487
488
489
490
491
492
493
494
495
    }
    return watchFd;
}


bool Foam::fileMonitor::removeWatch(const label watchFd)
{
    if (debug)
    {
        Pout<< "fileMonitor : removing watch " << watchFd << " on file "
            << watchFile_[watchFd] << endl;
    }

496
497
498
499
    if (!freeWatchFds_.found(watchFd))
    {
        freeWatchFds_.append(watchFd);
    }
500
    return watcher_->removeWatch(watchFd);
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
}


const Foam::fileName& Foam::fileMonitor::getFile(const label watchFd) const
{
    return watchFile_[watchFd];
}


Foam::fileMonitor::fileState Foam::fileMonitor::getState(const label watchFd)
const
{
    return state_[watchFd];
}


517
518
519
520
521
void Foam::fileMonitor::updateStates
(
    const bool masterOnly,
    const bool syncPar
) const
522
{
523
524
    if (Pstream::master() || !masterOnly)
    {
mattijs's avatar
mattijs committed
525
        // Update the localState_
526
527
        checkFiles();
    }
528
529
530

    if (syncPar)
    {
mattijs's avatar
mattijs committed
531
        // Pack local state (might be on master only)
532
533
        PackedList<2> stats(state_.size(), MODIFIED);
        if (Pstream::master() || !masterOnly)
534
        {
535
536
            forAll(state_, watchFd)
            {
537
                stats.set
mattijs's avatar
mattijs committed
538
                (
539
540
                    watchFd,
                    static_cast<unsigned int>(localState_[watchFd])
mattijs's avatar
mattijs committed
541
                );
542
            }
543
        }
544
545
546
547


        // Scatter or reduce to synchronise state
        if (masterOnly)
548
        {
549
550
551
552
553
554
555
556
557
            // Scatter
            if (stats.storage().size() == 1)
            {
                Pstream::scatter(stats.storage()[0]);
            }
            else
            {
                Pstream::listCombineScatter(stats.storage());
            }
558
559
560
        }
        else
        {
561
562
563
564
565
566
567
568
569
570
571
572
573
574
            // Reduce
            if (stats.storage().size() == 1)
            {
                // Optimisation valid for most cases.
                reduce(stats.storage()[0], reduceFileStates());
            }
            else
            {
                Pstream::listCombineGather
                (
                    stats.storage(),
                    combineReduceFileStates()
                );
            }
575
        }
576

577

mattijs's avatar
mattijs committed
578
        // Update synchronised state
579
        forAll(state_, watchFd)
580
        {
mattijs's avatar
mattijs committed
581
582
583
584
585
            // Assign synchronised state
            unsigned int stat = stats[watchFd];
            state_[watchFd] = fileState(stat);

            if (!masterOnly)
586
            {
mattijs's avatar
mattijs committed
587
588
                // Give warning for inconsistent state
                if (state_[watchFd] != localState_[watchFd])
589
                {
mattijs's avatar
mattijs committed
590
                    if (debug)
591
                    {
mattijs's avatar
mattijs committed
592
593
                        Pout<< "fileMonitor : Delaying reading "
                            << watchFile_[watchFd]
594
595
596
597
                            << " due to inconsistent "
                               "file time-stamps between processors"
                            << endl;
                    }
mattijs's avatar
mattijs committed
598

599
600
                    WarningInFunction
                        << "Delaying reading " << watchFile_[watchFd]
mattijs's avatar
mattijs committed
601
602
                        << " due to inconsistent "
                           "file time-stamps between processors" << endl;
603
604
605
606
                }
            }
        }
    }
607
608
609
610
    else
    {
        state_ = localState_;
    }
611
612
613
614
615
}


void Foam::fileMonitor::setUnmodified(const label watchFd)
{
616
    state_[watchFd] = UNMODIFIED;
mattijs's avatar
mattijs committed
617
    localState_[watchFd] = UNMODIFIED;
618
619
620

    if (!useInotify_)
    {
621
        watcher_->lastMod_[watchFd] = highResLastModified(watchFile_[watchFd]);
622
    }
623
624
625
626
}


// ************************************************************************* //