aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/skyframe/MacOSXFsEventsDiffAwareness.java
blob: 17a9413dccdaa10ead040fc7b3d518a75d9bfbef (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
// Copyright 2016 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.skyframe;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.UnixJniLoader;
import com.google.devtools.common.options.OptionsClassProvider;
import java.io.File;
import java.nio.file.Path;

/**
 * A {@link DiffAwareness} that use fsevents to watch the filesystem to use in lieu of
 * {@link LocalDiffAwareness}.
 *
 * <p>On OS X, the local diff awareness cannot work because WatchService is dummy and do polling,
 * which is slow (https://bugs.openjdk.java.net/browse/JDK-7133447).
 */
public final class MacOSXFsEventsDiffAwareness extends LocalDiffAwareness {
  private final double latency;

  private boolean closed;

  // Keep a pointer to a native structure in the JNI code (the FsEvents callback needs that
  // structure).
  private long nativePointer;

  private boolean opened;

  /**
   * Watch changes on the file system under <code>watchRoot</code> with a granularity of
   * <code>delay</code> seconds.
   */
  MacOSXFsEventsDiffAwareness(String watchRoot, double latency) {
    super(watchRoot);
    this.latency = latency;
  }

  /**
   * Watch changes on the file system under <code>watchRoot</code> with a granularity of 5ms.
   */
  MacOSXFsEventsDiffAwareness(String watchRoot) {
    this(watchRoot, 0.005);
  }

  /**
   * Helper function to start the watch of <code>paths</code>, called by the constructor.
   */
  private native void create(String[] paths, double latency);

  /**
   * Run the main loop
   */
  private native void run();

  private void init() {
    // The code below is based on the assumption that init() can never fail, which is currently the
    // case; if you change init(), then you also need to update {@link #getCurrentView}.
    Preconditions.checkState(!opened);
    opened = true;
    create(new String[] {watchRootPath.toAbsolutePath().toString()}, latency);
    // Start a thread that just contains the OS X run loop.
    new Thread(
            new Runnable() {
              @Override
              public void run() {
                MacOSXFsEventsDiffAwareness.this.run();
              }
            })
        .start();
  }

  /**
   * Close this watch service, this service should not be used any longer after closing.
   */
  @Override
  public void close() {
    if (opened) {
      Preconditions.checkState(!closed);
      closed = true;
      doClose();
    }
  }

  private static final boolean JNI_AVAILABLE;

  /**
   * JNI code stopping the main loop and shutting down listening to FSEvents.
   */
  private native void doClose();

  /**
   * JNI code returning the list of absolute path modified since last call.
   */
  private native String[] poll();

  static {
    boolean loadJniWorked = false;
    try {
      UnixJniLoader.loadJni();
      loadJniWorked = true;
    } catch (UnsatisfiedLinkError ignored) {
      // Unfortunately, we compile this class into the Bazel bootstrap binary, which doesn't have
      // access to the JNI code (to simplify bootstrap). This is the quick and dirty way to
      // hard-disable --watchfs in the bootstrap binary.
    }
    JNI_AVAILABLE = loadJniWorked;
  }

  @Override
  public View getCurrentView(OptionsClassProvider options)
      throws BrokenDiffAwarenessException {
    if (!JNI_AVAILABLE) {
      return EVERYTHING_MODIFIED;
    }
    // See WatchServiceDiffAwareness#getCurrentView for an explanation of this logic.
    boolean watchFs = options.getOptions(Options.class).watchFS;
    if (watchFs && !opened) {
      init();
    } else if (!watchFs && opened) {
      close();
      throw new BrokenDiffAwarenessException("Switched off --watchfs again");
    } else if (!opened) {
      // The only difference with WatchServiceDiffAwareness#getCurrentView is this if; the init()
      // call above can never fail, so we don't need to re-check the opened flag after init().
      return EVERYTHING_MODIFIED;
    }
    Preconditions.checkState(!closed);
    ImmutableSet.Builder<Path> paths = ImmutableSet.builder();
    for (String path : poll()) {
      paths.add(new File(path).toPath());
    }
    return newView(paths.build());
  }
}