I've recently tried playing around with the standard FastCGI APIs for a project, but it looks like I can't get it to work in "threaded" mode where multiple threads can handle long requests simultaneously.

When I start X threads starting from a port Y that increments for each new thread, I can only connect via the last port X + Y.
I can see it cycle through all the threads as """expected""" for each request, but it only does so for a single request at once, across all threads, until I call `FinishFastCGIRequest` or `WaitFastCGIRequest`.
Is there some hidden globally shared Mutex for the FastCGI APIs, or am I missing something here ?
-----------
If you want to test on your side, you can use the following code and Docker stack to setup a basic unsecure nginx server.
It should work just fine on Windows.
CgiServerTest.pb
Code: Select all
;- Compiler directives & imports
EnableExplicit
CompilerIf #PB_Compiler_Thread = 0
CompilerError "Required: Thread-safe compiler flag."
CompilerEndIf
;- Constants & Structures
#WorkerPool_Size = 2
#WorkerPool_PostBirthDelay = 0
#WorkerPool_PostThreadCheckDelay = 10
#WorkerPool_PostFullLoopDelay = 500
#Cgi_PortPoolStart = 5600
Structure ThreadParameters
FcgiPort.w
EndStructure
;- Macros
Macro WriteCGIConstant(Constant)
WriteCGIString((Constant) + ": " + CGIVariable(Constant)+"<br>")
EndMacro
;- Procedures
Procedure CustomWorkerThread(*Parameters.ThreadParameters)
If Not InitFastCGI(*Parameters\FcgiPort)
Debug "Failed to call 'InitFastCGI(" + Str(*Parameters\FcgiPort) + ")' !"
End 3
EndIf
PrintN("Started FastCGI on port " + Str(*Parameters\FcgiPort) + " !")
While WaitFastCGIRequest()
Debug "START - " + Str(*Parameters\FcgiPort)
If ReadCGI()
WriteCGIHeader(#PB_CGI_HeaderContentType, "text/html", #PB_UTF8)
WriteCGIHeader("HeaderTest", "HeaderTestValue", #PB_UTF8 | #PB_CGI_LastHeader)
WriteCGIString("<html><title>PureBasic - FastCGI</title><body>" +
"Hello from PureBasic FastCGI !<br>" +
"Actual time: <b>"+FormatDate("%hh:%ii", Date()) + "</b><br>" +
"Internal Port: <b>"+Str(*Parameters\FcgiPort)+ "</b><br>")
WriteCGIString("</body></html>")
FinishFastCGIRequest()
Define LoopStart.q = ElapsedMilliseconds()
While ElapsedMilliseconds() < LoopStart + 5000
Delay(1)
Wend
;Delay(5000)
EndIf
Debug "END - " + Str(*Parameters\FcgiPort)
Wend
EndProcedure
;- Code
;{
If Not OpenConsole()
End 1
EndIf
If Not InitCGI()
Debug "Failed to call 'InitCGI' !"
End 2
EndIf
; If Not InitCGI()
; End 2
; EndIf
NewList WorkerPoolThreadIDs.i()
NewList WorkerPoolThreadParams.i()
Define iWorkerPool.i
Define *TmpThreadParams.ThreadParameters
; Initializing the worker lists...
For iWorkerPool = 0 To #WorkerPool_Size - 1
InsertElement(WorkerPoolThreadIDs())
WorkerPoolThreadIDs() = #Null
InsertElement(WorkerPoolThreadParams())
WorkerPoolThreadParams() = #Null
Next
iWorkerPool = 0
Define StartTimeTest.q = ElapsedMilliseconds()
Define Quit.b = #False
Define iThread.i = 0
; Spawning the threads, and respawning dead ones
While Quit = #False
iThread = 0
ForEach WorkerPoolThreadIDs()
If Not IsThread(WorkerPoolThreadIDs())
; Cleaning up some leftovers from previous threads
SelectElement(WorkerPoolThreadParams(), ListIndex(WorkerPoolThreadIDs()))
If WorkerPoolThreadParams()
FreeMemory(WorkerPoolThreadParams())
EndIf
WorkerPoolThreadParams() = AllocateMemory(SizeOf(ThreadParameters))
*TmpThreadParams = WorkerPoolThreadParams()
;*TmpThreadParams\FcgiPort = #Cgi_PortPoolStart
*TmpThreadParams\FcgiPort = #Cgi_PortPoolStart + iThread
WorkerPoolThreadIDs() = CreateThread(@CustomWorkerThread(), *TmpThreadParams)
; Staggers the launch of future threads (not required !)
CompilerIf #WorkerPool_PostBirthDelay <> 0
Delay(#WorkerPool_PostBirthDelay)
CompilerEndIf
EndIf
CompilerIf #WorkerPool_PostThreadCheckDelay <> 0
Delay(#WorkerPool_PostThreadCheckDelay)
CompilerEndIf
iThread = iThread + 1
Next
CompilerIf #WorkerPool_PostFullLoopDelay <> 0
Delay(#WorkerPool_PostFullLoopDelay)
CompilerEndIf
Wend
; Waiting for the last threads to finish...
ForEach WorkerPoolThreadIDs()
If IsThread(WorkerPoolThreadIDs())
WaitThread(WorkerPoolThreadIDs())
EndIf
Next
Define EndTimeTest.q = ElapsedMilliseconds()
; Cleaning up...
ForEach WorkerPoolThreadParams()
If WorkerPoolThreadParams()
FreeMemory(WorkerPoolThreadParams())
EndIf
Next
Debug "Done !"
;}
docker-compose.yml
Code: Select all
services:
www_test_cgi:
container_name: www-test-cgi
build:
context: .
ports:
- 5002:80
environment:
- TZ=Europe/Brussels
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./default.conf:/etc/nginx/conf.d/default.conf:ro
- ./htdocs:/var/www/localhost/htdocs:ro
restart: unless-stopped
stop_grace_period: 1s
labels:
- "com.centurylinklabs.watchtower.enable=true"
logging:
driver: "json-file"
options:
max-size: "16m"
Dockerfile
Code: Select all
FROM nginx:alpine
# Install fcgiwrap for FastCGI support
RUN apk add --no-cache fcgiwrap spawn-fcgi
# Create directory for FastCGI sockets
RUN mkdir -p /var/run/fcgiwrap
# Copy custom nginx configuration
#COPY nginx.conf /etc/nginx/nginx.conf
#COPY default.conf /etc/nginx/conf.d/default.conf
# Copy your FastCGI application/script
#COPY cgi-bin/ /usr/lib/cgi-bin/
# Make CGI scripts executable
#RUN chmod +x /usr/lib/cgi-bin/*
# Expose port
EXPOSE 80
# Start both nginx and fcgiwrap
CMD spawn-fcgi -s /var/run/fcgiwrap/fcgiwrap.sock -P /var/run/fcgiwrap/fcgiwrap.pid /usr/bin/fcgiwrap && \
nginx -g "daemon off;"
nginx.conf
Code: Select all
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
}
default.conf
Code: Select all
server {
listen 80;
server_name localhost;
# Static files
location / {
root /var/www/localhost/htdocs;
index index.html index.htm;
}
# FastCGI configuration
location /test/ {
fastcgi_cache off;
fastcgi_param REQUEST_METHOD "TEST";
#fastcgi_param test123 "Test123456";
fastcgi_param HTTP_HOST phabricator.localhost1;
fastcgi_param HTTP_HOST phabricator.localhost2;
fastcgi_index index.php;
fastcgi_param REDIRECT_STATUS 200;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param NP_TEST_01 "Value_NP_TEST_01";
# Only works with the last open port
fastcgi_pass host.docker.internal:5601;
#fastcgi_index index.php;
}
}