-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Concurrency issue in typedthreads.nim #24591
Comments
Example with use of manual allocation: Results with MM ORC: SUMMARY: ThreadSanitizer: data race typedthreads.nim:154 in typedthreads::running(Thread<refnet::SocketImpl>) --mm:none: SUMMARY: ThreadSanitizer: data race typedthreads.nim:271 in typedthreads::createThread(var<Thread<refnet::SocketImpl>>, proc<refnet::SocketImpl>, refnet::SocketImpl) Nim Compiler Version 2.3.1 import
std/net,
std/locks,
std/os
type SocketOpts* = object
socket*: Socket
var lock: Lock = Lock()
proc processSession(sock: Socket) {.thread.} =
if sock.isNil:
withLock lock:
debugEcho "Client socket is nil"
return
withLock lock:
debugEcho "Thread ", getThreadId(), " got request to send to socket ", cast[uint16](sock[])
discard trySend(sock, "hello\n")
sleep(1000)
withLock lock:
sock.close
try:
initLock(lock)
except:
#log error
debugEcho "Unable to init rlock"
var
thr: array[0..10, Thread[Socket]]
sockets: array[0..10, Socket]
address: string = ""
client: Socket
for sock in sockets.mitems:
sock = cast[Socket](alloc(sizeof(SocketImpl)))
proc startServer*(srv: var SocketOpts): void =
try:
srv.socket = newSocket()
srv.socket.setSockOpt(OptReusePort, true)
srv.socket.setSockOpt(OptNoDelay, true, level = IPPROTO_TCP.cint)
srv.socket.bindAddr(Port(3333))
srv.socket.listen()
var id: int = 0
while true:
srv.socket.acceptAddr(client, address)
withLock lock:
if thr[id].running:
debugEcho "No threads available"
client.send("No threads available")
client.close()
continue
deepCopy(sockets[id], client)
echo "Client connected from: ", address, ". Socket: ", cast[uint16](sockets[id][])
createThread(thr[id], processSession, sockets[id])
client = Socket()
if id == thr.high:
id = 0
else:
id.inc
except:
#log error here!!!
debugEcho "ERROR!"
deinitLock lock
let excpt: ref Exception = getCurrentException()
debugEcho excpt.msg
return
deinitLock(lock)
var params: SocketOpts
startServer(params)
|
I reworked the code to use channels and not create new threads on each socket. Thread-sanitizer with my macOS clang Also I fixed the lock init. Couple of notes. Unfortunately import
std/net,
std/locks,
std/os,
std/isolation
import threading/channels
var
lock: Lock
busy: array[10, bool]
lock.initLock()
type SocketOpts* = object
socket*: Socket
proc processSession(thChan: Chan[(Socket, int)]) {.thread.} =
var thChan = thChan # needed to ensure we have a local copy
while true:
var sockArg: (Socket, int) = thChan.recv()
let sock = sockArg[0]
let id = sockArg[1]
withLock lock:
busy[id] = true
echo "\tThread ", id, " got request to send to socket ", sock.getFd().int
discard trySend(sock, "hello\n")
os.sleep(1000)
sock.close()
withLock lock:
echo "\tThread ", getThreadId(), " done"
busy[id] = false
try:
initLock(lock)
except:
#log error
debugEcho "Unable to init rlock"
var
thr: array[10, Thread[Chan[(Socket, int)]]]
chans: array[10, Chan[(Socket, int)]]
address: string = ""
for id in 0..<chans.len():
try:
chans[id] = newChan[(Socket, int)]()
withLock lock:
busy[id] = false
createThread(thr[id], processSession, chans[id])
except:
quit 1
proc startServer*(srv: var SocketOpts): void =
try:
srv.socket = newSocket()
srv.socket.setSockOpt(OptReusePort, true)
srv.socket.setSockOpt(OptNoDelay, true, level = IPPROTO_TCP.cint)
srv.socket.bindAddr(Port(3333))
srv.socket.listen()
while true:
var client: Socket = Socket()
srv.socket.acceptAddr(client, address)
echo "Got connection "
var wasSent = false
for id in 0..<chans.len():
echo "checking thread: ", id
var isBusy: bool
withLock lock:
isBusy = busy[id]
if isBusy:
echo "thread busy: ", id
continue
else:
var sm: (Socket, int)
sm[0] = move client
sm[1] = id
chans[id].send(unsafeIsolate move sm)
echo "sent to thread: ", id
wasSent = true
break
if not wasSent and client != nil:
echo "No threads available"
# client.send("No threads available")
client.close()
continue
except:
#log error here!!!
debugEcho "ERROR!"
deinitLock lock
let excpt: ref Exception = getCurrentException()
debugEcho excpt.msg
return
deinitLock(lock)
var params: SocketOpts
startServer(params) |
I believe The second example in this issue would need to do something like thread.running should be made into an atomic bool operation. |
Thanks Elcritch!
I did, just after implementing the processSession(). Does it matter where the initLock(lock) is placed? |
Ah, my bad I saw the No it shouldn't matter as long as it's before it's used. |
Oh weird. It really shouldn't matter, but looks like my version actually did have your You could try putting the initLock in your main proc. |
I took a stab at a PR to fix the t-san issues with typedthreads |
Please ignore my mistake. I forgot to copy nim.cfg (with T-san enabled) into the directory with the test code. |
Additional version with manually allocated targs in the heap, without using thread.running. This code ran for about 2 hours under high load, but after about an hour T-san reported the following problem:
More likely to be solved with @elcritch proposed PR, but might be helpful for testing anyway. import
std/net,
std/locks,
std/os
type SocketOpts* = object
socket*: Socket
var lock: Lock = Lock()
try:
initLock(lock)
except:
#log error
debugEcho "Unable to init rlock"
proc processSession(sock: ptr tuple[sock: ptr Socket, running: bool]) {.thread.} =
withLock lock:
if sock[0].isNil:
debugEcho "Client socket is nil"
return
withLock lock:
sock[1] = true
debugEcho "Thread ", getThreadId(), " got request to send to socket ", cast[uint16](sock[0][][])
discard trySend(sock[0][], "hello\n")
sleep(1000)
withLock lock:
sock[0][].close
sock[1] = false
#cast[array[0..10, Thread[ptr tuple[sock: Socket, running: bool]]]](alloc(sizeof(array[0..10, Thread[ptr tuple[sock: Socket, running: bool]]])))
var
thr: array[0..10, Thread[ptr tuple[sock: ptr Socket, running: bool]]]
sockets: array[0..10, tuple[sock: ptr Socket, running: bool]]
address: string = ""
client: Socket
for sock in sockets.mitems:
sock[0]= cast[ ptr Socket](alloc(sizeof(SocketImpl)))
sock[1] = cast[bool](alloc(sizeof(bool)))
sock[1] = false
proc startServer*(srv: var SocketOpts): void =
try:
srv.socket = newSocket()
srv.socket.setSockOpt(OptReusePort, true)
srv.socket.setSockOpt(OptNoDelay, true, level = IPPROTO_TCP.cint)
srv.socket.bindAddr(Port(3333))
srv.socket.listen()
var id: int = 0
while true:
srv.socket.acceptAddr(client, address)
withLock lock:
if sockets[id][1]:
debugEcho "No threads available"
client.send("No threads available")
client.close()
continue
deepCopy(sockets[id][0][], client)
echo "Client connected from: ", address, ". Socket: ", cast[uint16](sockets[id][0][][])
createThread(thr[id], processSession, sockets[id].unsafeAddr)
client = Socket()
if id == thr.high:
id = 0
else:
id.inc
except:
#log error here!!!
debugEcho "ERROR!"
deinitLock lock
let excpt: ref Exception = getCurrentException()
debugEcho excpt.msg
return
deinitLock(lock)
var params: SocketOpts
startServer(params) |
Description
Thread sanitiser and valgrind (--tool=helgrind) report a data race (probably read) in typedthreads.nim on line 274 (Using devel version of the compiler. The problem also occurs with other compiler versions, but the reported line numbers vary.).
Example using channels:
Example of using an array to store client sockets:
nim.cfg :
Nim Version
Current devel version,
2.2.0
1.6.20
Current Output
Expected Output
No response
Known Workarounds
No response
Additional Information
To test the code, use the following tools in two separate terminals
watch -n 0.1 nc 127.0.0.1 3333
To test with valgrind (--tool=helgrind), the thread sanitiser should be disabled.
The text was updated successfully, but these errors were encountered: