Skip to content

Why do both Java and Go lock socket before write?

The Go internal/poll/fd_unix.go code is here

// Write implements io.Writer.
func (fd *FD) Write(p []byte) (int, error) {
    if err := fd.writeLock(); err != nil {
        return 0, err
    }
    defer fd.writeUnlock()
    ......
}

the java code java.net.SocketOutputStream#socketWrite is here

 private void socketWrite(byte b[], int off, int len) throws IOException {
        if (len <= 0 || off < 0 || len > b.length - off) {
            if (len == 0) {
                return;
            }
            throw new ArrayIndexOutOfBoundsException("len == " + len
                    + " off == " + off + " buffer length == " + b.length);
        }

        FileDescriptor fd = impl.acquireFD();
        try {
            socketWrite0(fd, b, off, len);
        } catch (SocketException se) {
        ......

I don’t know why we need to lock that. Another question is the syscall.Write equivalent to <unistd.h> write in C?

Answer

Well, only answering for the Java part:

As we are already talking about implementation details, why stop at the Java level? The C Source code for OpenJdk implementation of the native method socketWrite0 spans ~70 lines of code and is clearly not atomic. It does things like allocating and deallocating memory using malloc and free, among other things, and involves quite a bit of non-trivial logic. Whether or not the NET_Send function it invokes to actually send data directly translates to a syscall on every supported platform hardly matters anymore, at that point.

The main point is: the implementation invokes this NET_Send-function in a loop. So whether or not this is threadsafe individually, if it is invoked concurrently by multiple threads, the output will be interleaved (best case).