Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sockets with Safe Disconn deadlock when deepcopied between threads #24648

Open
albassort opened this issue Jan 25, 2025 · 1 comment
Open

Sockets with Safe Disconn deadlock when deepcopied between threads #24648

albassort opened this issue Jan 25, 2025 · 1 comment

Comments

@albassort
Copy link

Description

import net
import os
var address = ""
var client: Socket

let socket = newSocket()
socket.setSockOpt(OptReusePort, true)
socket.bindAddr(Port(7512))
socket.listen()
var channel : Channel[Socket]
channel.open()
proc thread1() {.gcsafe.} =
  while true:
    let recv = channel.tryRecv()
    if not recv.dataAvailable:
      sleep 5
      continue
    var socket : Socket
    block scope:
      let data = recv.msg
      # To remove data from any shared scope after we deepcopy
      socket = deepCopy(data)
    while true:
      socket.send("!!!\r\L")
      echo "send!"
      sleep 500

proc thread2() {.gcsafe.} =
  sleep 1000
  let socket = newSocket()
  socket.connect("localhost", Port(7512))
  while true:
    echo socket.recvLine()
    sleep 100
    socket.close()
    break

var t1 : Thread[void]
var t2 : Thread[void]
createThread(t1, thread1)
createThread(t2, thread2)

while true:
  socket.accept(client)
  channel.send(client)

Nim Version

2.2.0

Current Output

==STDOUT START==
send!
!!!
send!
==STDOUT END==
(deadlocks here)

Expected Output


Known Workarounds

Remove the safedisconnect flag

Additional Information

So I know why this happens, and how to fix it. However, no PR due to the fact that I don't know what is an acceptable solution.

socketError(socket, lastError = lastError, flags = flags)

The issue occurs under the following scenario

- SafeDisconn flag is present
&
- Cannot write to socket (because it is closed)
& 
-  socket.getFd() != osInvalidSocket (because its copied btween threads)

A solution would be explicit source control. E.g:

      if not isBlockingErr:
        let lastError = osLastError()
        socketError(socket, lastError = lastError, flags = flags)
        #But the user has no way of knowing that the socket is closed
        #And that they should remove the safe disconn flag 
        break

or perhaps

if not isBlockingErr:
  let lastError = osLastError()
  socketError(socket, lastError = lastError, flags = flags)
  attemps.inc()
  if attempts > maxRetries:
    raiseOSError(osLastError(), "Could not send all data.")

But this defeats the purpose of the safe disconn flag

Another way, more fitting way, would be to detect if its copied between threads, and get the new FD for it. Or, alternatively, just find a better way of testing if its open.

@mk1nz
Copy link

mk1nz commented Jan 25, 2025

It's not a deadlock. The problem with this code is that accept() is blocking while thread2() is exiting and nothing is going to process a new request.
The following code works:

import net
import os
var address = ""
var client: Socket

let globalSocket = newSocket()
globalSocket.setSockOpt(OptReusePort, true)
globalSocket.bindAddr(Port(7512))
globalSocket.listen()
var channel : Channel[Socket]
channel.open()
proc thread1() {.gcsafe.} =
  while true:
    let recv = channel.tryRecv()
    if not recv.dataAvailable:
      sleep 5
      continue
    var socket : Socket
    var data = Socket()
    block scope:
      let data = recv.msg
      # To remove data from any shared scope after we deepcopy
      deepCopy(socket, data)
    while true:
      if not socket.trySend("!!!\r\L"):
        echo "Unable to send"
        socket.close()
        return
      echo "send!"
      sleep 500

proc thread2() {.gcsafe.} =
  {.gcsafe.}:
    sleep 1000
    let socket = newSocket()
    socket.connect("localhost", Port(7512))
    while true:
      echo socket.recvLine()
      sleep 100
      socket.close()
      break
    globalSocket.close
    return

var t1 : Thread[void]
var t2 : Thread[void]
createThread(t1, thread1)
createThread(t2, thread2)

var toThrSock: seq[Socket]
while true:
  try:
    globalSocket.accept(client)
    toThrSock.add Socket()
    deepCopy(toThrSock[toThrSock.high], client)
    channel.send(toThrSock[toThrSock.high])
  except:
    echo "Unable to accept request"
    joinThread(t1)
    break

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants