How do I fix this race condition?

穿精又带淫゛_ 提交于 2020-02-05 06:21:28

问题


I have a server accepting clients that has a stop() method that closes the server down, which is causing a java.nio.AsynchronousCloseException that I'd like to resolve. The stop() method is called on a different thread, which is what is causing the race condition I believe.

Here is my code:

public void run() {
    InetSocketAddress addr = new InetSocketAddress(provider.getConnection(), 12354);
    try {
        server = ServerSocketChannel.open();
        server.configureBlocking(true);
        server.socket().bind(addr);
        parent.setIP(addr.getAddress().getHostAddress().toString());
        password = generatePassword();
        parent.setPassword(password);
        parent.setStatus("Ready.");
    } catch (IOException e) {
        parent.die("Could not start server: " + e.getMessage());
        runner = null;
    }
    while (runner == Thread.currentThread()) {
        try {
            SocketChannel sc = server.accept();
            if (available) {
                session = new ReceiveSession(this, sc, password, addr.getAddress());
                session.start();
                available = false;
            } else {
                new ReceiveBusyHandler(sc).start();
            }
        } catch (IOException e) {
            synchronized (swallowException) {
                if (!swallowException) {
                    parent.showError(e.toString());
                }
                available = true;
            }
        }
    }
}

public void stop() throws IOException {
    synchronized (swallowException) {
        swallowException = true;
        runner = null;
        if (server != null) {
            server.socket().close();
            server.close();
        }

        swallowException = false;
        System.out.println("Server down");
    }
}

(FYI, swallowException is a Boolean and you can see I've tried synchronizing it.)

It looks like the stop() method is setting swallowException to true and then back to false before the exception handler in my server loop has a chance to access it.

UPDATE: I introduced a new Object to use as a lock, and used wait()/notify() to fix my issue:

public void run() {
        InetSocketAddress addr = new InetSocketAddress(provider.getConnection(), 12354);
        try {
            server = ServerSocketChannel.open();
            server.configureBlocking(true);
            server.socket().bind(addr);
            parent.setIP(addr.getAddress().getHostAddress().toString());
            password = generatePassword();
            parent.setPassword(password);
            parent.setStatus("Ready.");
        } catch (IOException e) {
            parent.die("Could not start server: " + e.getMessage());
            runner = null;
        }
        while (runner == Thread.currentThread()) {
            try {
                SocketChannel sc = server.accept();
                if (available) {
                    session = new ReceiveSession(this, sc, password, addr.getAddress());
                    session.start();
                    available = false;
                } else {
                    new ReceiveBusyHandler(sc).start();
                }
            } catch (IOException e) {
                synchronized (lock) {
                    if (!swallowException) {
                        parent.showError(e.toString());

                    }
                    lock.notify();
                    available = true;
                }
            }
        }
    }

    public void stop() throws IOException {
        synchronized (lock) {
            swallowException = true;
            runner = null;
            if (server != null) {
                server.socket().close();
                server.close();
            }
            while (swallowException) {
                try {
                    lock.wait();
                    swallowException = false;
                } catch (InterruptedException e) {
                }
            }
            //swallowException = false;
            System.out.println("Server down");
        }
    }

回答1:


In Java, synchronization is done on an object, not on a variable. When you synchronize on swallowException, you synchronize on its value (Boolean.TRUE or Boolean.FALSE). This is not what you want. You should synchronize on the object that contains swallowException.




回答2:


This part is not correctly synchronized:

synchronized (swallowException) {
    swallowException = true;

You are synchronizing on one instance (false) and immediately changing the swallowException reference to point to a different instance (true). The next thread to enter stop won't block.

Either synchronize on an instance that won't be swapped out (the owner of these methods) or use some other locking mechanism from java.util.concurrent.




回答3:


I would strongly advise refactoring your code (even the solution you posted as an update), because it's just not clear what's happening.

From your description, it seems like you just want a thread-safe way to stop your server. I recommend doing this, whereby you simply call close() on the ServerSocket, and be able to catch a SocketException:

private boolean cont = true;

// this can safely be called from any thread
public synchronized void stop() {
    cont = false;
    if (server != null) {
       server.socket().close();
    }
}
private synchronized void setContinue(boolean value) {
    cont = value;
}
private synchronized boolean shouldContinue() {
    return cont;
}
private synchronized void openChannel() {
    server = ServerSocketChannel.open();
}

public void run() {
    InetSocketAddress addr = new InetSocketAddress(provider.getConnection(), 12354);
    try {
        openChannel();
        server.configureBlocking(true);
        server.socket().bind(addr);
        parent.setIP(addr.getAddress().getHostAddress().toString());
        password = generatePassword();
        parent.setPassword(password);
        parent.setStatus("Ready.");
    } catch (IOException e) {
        parent.die("Could not start server: " + e.getMessage());
        setContinue(false);
    }

    while (shouldContinue()) {
        try {
            SocketChannel sc = server.accept();
            if (shouldContinue()) {
                if (available) {
                    session = new ReceiveSession(this, sc, password, addr.getAddress());
                    session.start();
                    available = false;
                } else {
                    new ReceiveBusyHandler(sc).start();
                }
            }
        } catch (SocketException se) {
            // normal shutdown from stop()
        } catch (IOException e) {
            parent.showError(e.toString()); 
            available = true;               
        }
    }
    System.out.println("Server down");
}

See more about this technique for stopping a server here



来源:https://stackoverflow.com/questions/16261974/how-do-i-fix-this-race-condition

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!