0

The main code

I have a method which uses a service in order to push a job and wait for such service to create a file somewhere.

Such service exposes two methods:

  • String pushJob(...) <-- will push the job and will return the job id as a string
  • boolean isJobReady(String id) <-- will return true if job is finished, false otherwise

In order to wait, this method does something like this:

CompletableFuture<Void> waiter = new CompletableFuture<>();
waitFor(waiter, jobId); //<-- this will poll on the isJobReady endpoint until it responds with true
                        //    when this happens, it will complete the future.
waiter.get(1, TimeUnit.HOUR); //<-- wait for the job to complete within the hour
//do something with the file that was created once the get returns without exceptions

The unit test

This works fine, but I would like to test it. Hence, I've made a mock implementation of the service exposing the two methods pushJob and isJobReady:

public class MockService implements Service {

    private volatile boolean isReady;

    @Override
    public String pushJob() {
        isReady = false;
        CompletableFuture.runAsync(() -> {
            Thread.sleep(10000); //<-- simulates the time spent to perform the job on service side
            File file = new File("..."); 
            if (file.createNewFile()) {
                isReady = true;
            } else {
                throw new IllegalStateException("Could not create test file");
            }
        });
        return "12345";
    }

    @Override
    public boolean isJobReady(String id) {
        return isReady;
    }
}

I then inject such mock implementation inside my code in order to test it.

The issue

After the mock implementation creates the file, it sets the volatile field isReady to true so when the main code sees it, it will right after go and search for the file that was created.

However, it finds it once out of two times.

If I set a breakpoint right before searching for the file and wait a second, the code will systematically succeed. But if I go at normal execution speed (or simply compile time), the test turns to be extremely flaky.

I figure out that this is a multi-threading issue but I really don't see how it is possible because the test code will not set the variable isReady = true until when it has a response from createNewFile() which (according to the javadoc) Atomically creates a new, empty file named by this abstract pathname if and only if a file with this name does not yet exist.

Why is this happening and how can I correctly synchronize the file system so that the creation of the file inside the mock service is correctly visible on the main code?

Matteo NNZ
  • 11,930
  • 12
  • 52
  • 89

1 Answers1

0

The general way to rely on the file system seems to be wrong. I don't think you can expect the new file become immediately visible for everything and everybody.

I would extend the method isJobReady. It must additionally ensure, the file really exists and is visible for the working thread. Method isJobReady must return false otherwise (and maybe put a warning into log, if the situation is not synchronized with the RAM state).

UPDATE: Here is similar problem with only one thread. Atomicity doesn't belong to the strengths of file systems: Creating new files concurrently

30thh
  • 10,861
  • 6
  • 32
  • 42