aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/util/io/TimestampGranularityMonitor.java
blob: 09d00676e4bdf72ff1a32afb443b061abf86b4ea (plain)
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
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.devtools.build.lib.util.io;

import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.profiler.Profiler;
import com.google.devtools.build.lib.profiler.ProfilerTask;
import com.google.devtools.build.lib.util.Clock;

/**
 * A utility class for dealing with filesystem timestamp granularity issues.
 *
 * <p>
 * Consider a sequence of commands such as
 * <pre>
 *     echo ... &gt; foo/bar
 *     blaze query ...
 *     echo ... &gt; foo/bar
 *     blaze query ...
 * </pre>
 *
 * If these commands all run very fast, it is possible that the timestamp
 * on foo/bar is not changed by the second command, even though some time has
 * passed, because the times are the same when rounded to the file system
 * timestamp granularity (often 1 second).
 * For performance, we assume that files
 * timestamps haven't changed  can safely be cached without reexamining their contents.
 * But this assumption would be violated in the above scenario.
 *
 * <p>
 * To address this, we record the current time at the start of executing
 * a Blaze command, and whenever we check the timestamp of a source file
 * or BUILD file, we check if the timestamp of that source file matches
 * the current time.  If so, we set a flag.  At the end of the command,
 * if the flag was set, then we wait until the clock has advanced, so
 * that any file modifications performed after the command exits will
 * result in a different file timestamp.
 *
 * <p>
 * This class implicitly assumes that each filesystem's clock
 * is the same as either System.currentTimeMillis() or
 * System.currentTimeMillis() rounded down to the nearest second.
 * That is not strictly correct; there might be clock skew between
 * the cpu clock and the file system clocks (e.g. for NFS file systems),
 * and some file systems might have different granularity (e.g. the old
 * DOS FAT filesystem has TWO-second granularity timestamps).
 * Clock skew can be addressed using NTP.
 * Other granularities could be addressed by small changes to this class,
 * if it turns out to be needed.
 *
 * <p>
 * Another alternative design that we considered was to write to a file and
 * read its timestamp.  But doing that is a little tricky because we don't have
 * a FileSystem or Path handy.  Also, if we were going to do this, the stamp
 * file that is used should be in the same file system as the input files.
 * But the input file system(s) might not be writeable, and even if it is,
 * it's hard for Blaze to find a writable file on the same filesystem as the
 * input files.
 */
@ThreadCompatible
public class TimestampGranularityMonitor {

  /**
   * The time of the start of the current Blaze command,
   * in milliseconds.
   */
  private long commandStartTimeMillis;

  /**
   * The time of the start of the current Blaze command,
   * in milliseconds, rounded to one second granularity.
   */
  private long commandStartTimeMillisRounded;

  /**
   * True iff we detected a source file or BUILD file whose (unrounded)
   * timestamp matched the time at the start of the current Blaze command
   * rounded to the nearest second.
   */
  private volatile boolean waitASecond;

  /**
   * True iff we detected a source file or BUILD file whose timestamp
   * exactly matched the time at the start of the current Blaze command
   * (measuring both in integral numbers of milliseconds).
   */
  private volatile boolean waitAMillisecond;

  private final Clock clock;

  public TimestampGranularityMonitor(Clock clock) {
    this.clock = clock;
  }

  /**
   * Record the time at which the Blaze command started.
   * This is needed for use by waitForTimestampGranularity().
   */
  public void setCommandStartTime() {
    this.commandStartTimeMillis = clock.currentTimeMillis();
    this.commandStartTimeMillisRounded = roundDown(this.commandStartTimeMillis);
    this.waitASecond = false;
    this.waitAMillisecond = false;
  }

  /**
   * Record that the output of this Blaze command depended on the contents
   * of a build file or source file with the specified time stamp.
   */
  @ThreadSafe
  public void notifyDependenceOnFileTime(long mtime) {
    if (mtime == this.commandStartTimeMillis) {
      this.waitAMillisecond = true;
    }
    if (mtime == this.commandStartTimeMillisRounded) {
      this.waitASecond = true;
    }
  }

  /**
   * If needed, wait until the next "tick" of the filesystem timestamp clock.
   * This is done to ensure that files created after the current Blaze command
   * finishes will have timestamps different than files created before the
   * current Blaze command started.  Otherwise a sequence of commands
   * such as
   * <pre>
   *     echo ... &gt; foo/BUILD
   *     blaze query ...
   *     echo ... &gt; foo/BUILD
   *     blaze query ...
   * </pre>
   * could return wrong results, due to the contents of package foo
   * being cached even though foo/BUILD changed.
   */
  public void waitForTimestampGranularity(OutErr outErr) {
    if (this.waitASecond || this.waitAMillisecond) {
      long startedWaiting = Profiler.nanoTimeMaybe();
      boolean interrupted = false;

      if (waitASecond) {
        // 50ms slack after the whole-second boundary
        while (clock.currentTimeMillis() < commandStartTimeMillisRounded + 1050) {
          try {
            Thread.sleep(50 /* milliseconds */);
          } catch (InterruptedException e) {
            if (!interrupted) {
              outErr.printErrLn("INFO: Hang on a second...");
              interrupted = true;
            }
          }
        }
      } else {
        while (clock.currentTimeMillis() == commandStartTimeMillis) {
          try {
            Thread.sleep(1 /* milliseconds */);
          } catch (InterruptedException e) {
            if (!interrupted) {
              outErr.printErrLn("INFO: Hang on a millisecond...");
              interrupted = true;
            }
          }
        }
      }
      if (interrupted) {
        Thread.currentThread().interrupt();
      }

      Profiler.instance().logSimpleTask(startedWaiting, ProfilerTask.WAIT,
                                        "Timestamp granularity");
    }
  }

  /**
   * Rounds the specified time, in milliseconds, down to the nearest second,
   * and returns the result in milliseconds.
   */
  private static long roundDown(long millis) {
    return millis / 1000 * 1000;
  }

}