openresty / openresty/lua-nginx-module

UDP - sockets not duplex in threads

Open
#1,852 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C
Stars
11.8k
Forks
2.1k
Avg merge
6h 1m
Merged PRs (30d)
6

Description

Hi there, I believe I've found a bug with UDP sockets, or at least they don't behave the way I expect.

If I open a TCP connection to a service, I can spawn a reader thread and writer thread on the socket. If I send while the TCP Socket is reading, it works.

With a UDP socket, if I try to send while a UDP socket is reading, I receive a socket busy message.

Here's a few small programs to demonstrate the behavior:

First, a TCP echo server (in Python):

#!/usr/bin/env python3

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server_address = '0.0.0.0'
server_port = 31337

server = (server_address, server_port)
sock.bind(server)
sock.listen(1)
print("Listening on " + server_address + ":" + str(server_port))

connection, client_address = sock.accept()
print("Connection from " + str(client_address))

while True:
	payload = connection.recv(4096)
	if not payload:
		connection.close()
		break
	print(payload)
	sent = connection.sendall(payload)

And a similar UDP-based echo server, also in Python:

#!/usr/bin/env python3

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

server_address = '0.0.0.0'
server_port = 31337

server = (server_address, server_port)
sock.bind(server)
print("Listening on " + server_address + ":" + str(server_port))

while True:
	payload, client_address = sock.recvfrom(4096)
	print("Echoing data back to " + str(client_address))
	print(payload)
	sent = sock.sendto(payload, client_address)

Here's a client script (Lua) for connecting to TCP, sending packets, and reading responses:

local tcp = ngx.socket.tcp()
tcp:connect('127.0.0.1',31337)

local r = ngx.thread.spawn(function()
  while true do
    local packet, err = tcp:receiveany(4096)
    print(#packet,packet)
  end
end)

local w = ngx.thread.spawn(function()
  local i = 1
  local bytes, err
  while true do
    ngx.sleep(1)
    bytes, err = tcp:send('message ' .. i)
    if err then
      error(err)
    end
    i = i + 1
  end
end)

ngx.thread.wait(r,w)
ngx.thread.kill(r)
ngx.thread.kill(w)

And the equivalent for UDP:

local udp = ngx.socket.udp()
udp:setpeername('127.0.0.1',31337)

local r = ngx.thread.spawn(function()
  while true do
    local packet, err = udp:receive()
    print(#packet,packet)
  end
end)

local w = ngx.thread.spawn(function()
  local i = 1
  local bytes, err
  while true do
    ngx.sleep(1)
    bytes, err = udp:send('message ' .. i)
    if err then
      error(err)
    end
    i = i + 1
  end
end)

ngx.thread.wait(r,w)
ngx.thread.kill(r)
ngx.thread.kill(w)

When I run the TCP version, I get the expected behavior - about once per second, I send a string of text and receive it.

I'd expect the UDP version to behave the same, but it throws the socket busy error.

I'm running both of the Lua scripts with the resty CLI.

I'm on Arch Linux, here's my uname:

Linux hefty 5.10.9-rt24-xanmod1-1-xanmod-rt #1 SMP PREEMPT_RT Thu, 28 Jan 2021 16:44:29 +0000 x86_64 GNU/Linux

I'm using the latest release version of OpenResty (1.19.3.1), here's the output of openresty -V:

nginx version: openresty/1.19.3.1
built by gcc 10.2.0 (GCC) 
built with OpenSSL 1.1.1i  8 Dec 2020
TLS SNI support enabled
configure arguments: --prefix=/home/john/openresty/nginx --with-cc-opt='-O2 -I/home/john/openresty/include' --add-module=../ngx_devel_kit-0.3.1 --add-module=../echo-nginx-module-0.62 --add-module=../xss-nginx-module-0.06 --add-module=../ngx_coolkit-0.2 --add-module=../set-misc-nginx-module-0.32 --add-module=../form-input-nginx-module-0.12 --add-module=../encrypted-session-nginx-module-0.08 --add-module=../srcache-nginx-module-0.32 --add-module=../ngx_lua-0.10.19 --add-module=../ngx_lua_upstream-0.07 --add-module=../headers-more-nginx-module-0.33 --add-module=../array-var-nginx-module-0.05 --add-module=../memc-nginx-module-0.19 --add-module=../redis2-nginx-module-0.15 --add-module=../redis-nginx-module-0.3.7 --add-module=../rds-json-nginx-module-0.15 --add-module=../rds-csv-nginx-module-0.09 --add-module=../ngx_stream_lua-0.0.9 --with-ld-opt='-Wl,-rpath,/home/john/openresty/luajit/lib -s -Wl,-rpath,/home/john/openresty/lib:/home/john/openresty/luajit/lib -L/home/john/openresty/lib' --with-file-aio --with-ipv6 --with-md5-asm --with-pcre-jit --with-sha1-asm --with-threads --with-stream --with-stream_ssl_module --with-stream_ssl_preread_module --with-http_ssl_module

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the behavior with the Lua ngx.socket.udp and ngx.thread.spawn example under the resty CLI, comparing it with ngx.socket.tcp. Trace the UDP receive and send paths to identify why concurrent operations produce “socket busy.” Done means UDP send and receive can run in separate threads as shown, with a regression test covering the behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
lua, python
Domain
networking
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.