Make the proAdhocServer lighter
Anuskuss opened this issue · comments
What should happen
I'm running my own proAdhocServer (using this mirror) but PPSSPP suffers from the same issue, i.e. it's running in a loop which wastes CPU cycles. Right now my Adhoc server sits at 1-2% idle while lighttpd (a HTTP server) sits at a constant 0%. The only improvement that can be made with the current design is to increase the sleep (like @anr2me has already done here) but that only reduces the CPU usage to 0-0.7%.
My question is how do you actually put the thread to sleep and only wake it up once something tries to read/write from/to the socket/port (like you'd see with a lock)? I mean it has to be possible because lighttpd listens on port 80, runs at 0% AND also responds instantly. Just increasing the sleep further probably results in connection problems so I don't think that is the solution.
Who would this benefit
PPSSPP users that enable the built-in proAdhocServer. Also energy isn't free, battery life, heat etc.
Platform (if relevant)
None
Games this would be useful in
Other emulators or software with a similar feature
No response
Checklist
- Check the latest git build in case it's already implemented.
- Search for other requests of the same feature.
I wrote some shitty code that does what I want but I'm not confident in making those changes to PPSSPP:
--- a/src/main.c
+++ b/src/main.c
@@ -31,16 +31,19 @@
#include <config.h>
#include <user.h>
#include <status.h>
+#include <unistd.h>
// Server Status
-int _status = 0;
+int server;
+int status = 0;
+int users = 0;
// Function Prototypes
void interrupt(int sig);
void enable_address_reuse(int fd);
void change_blocking_mode(int fd, int nonblocking);
int create_listen_socket(uint16_t port);
-int server_loop(int server);
+int server_loop();
/**
* Server Entry Point
@@ -63,7 +66,7 @@ int main(int argc, char * argv[])
signal(SIGTERM, interrupt);
// Create Listening Socket
- int server = create_listen_socket(SERVER_PORT);
+ server = create_listen_socket(SERVER_PORT);
// Created Listening Socket
if(server != -1)
@@ -72,7 +75,7 @@ int main(int argc, char * argv[])
printf("Listening for Connections on TCP Port %u.\n", SERVER_PORT);
// Enter Server Loop
- result = server_loop(server);
+ result = server_loop();
// Notify User
printf("Shutdown complete.\n");
@@ -92,7 +95,8 @@ void interrupt(int sig)
printf("Shutting down... please wait.\n");
// Trigger Shutdown
- _status = 0;
+ status = 0;
+ close(server);
}
/**
@@ -146,7 +150,7 @@ int create_listen_socket(uint16_t port)
enable_address_reuse(fd);
// Make Socket Nonblocking
- change_blocking_mode(fd, 1);
+ //change_blocking_mode(fd, 1);
// Prepare Local Address Information
struct sockaddr_in local;
@@ -187,22 +191,22 @@ int create_listen_socket(uint16_t port)
* @param server Server Listening Socket
* @return OS Error Code
*/
-int server_loop(int server)
+int server_loop()
{
// Set Running Status
- _status = 1;
+ status = 1;
// Create Empty Status Logfile
update_status();
// Handling Loop
- while(_status == 1)
+ while(status == 1)
{
// Login Block
{
// Login Result
int loginresult = 0;
-
+
// Login Processing Loop
do
{
@@ -213,7 +217,7 @@ int server_loop(int server)
// Accept Login Requests
// loginresult = accept4(server, (struct sockaddr *)&addr, &addrlen, SOCK_NONBLOCK);
-
+
// Alternative Accept Approach (some Linux Kernel don't support the accept4 Syscall... wtf?)
loginresult = accept(server, (struct sockaddr *)&addr, &addrlen);
if(loginresult != -1)
@@ -223,7 +227,11 @@ int server_loop(int server)
}
// Login User (Stream)
- if(loginresult != -1) login_user_stream(loginresult, addr.sin_addr.s_addr);
+ if(loginresult != -1) {
+ users++;
+ change_blocking_mode(server, 1);
+ login_user_stream(loginresult, addr.sin_addr.s_addr);
+ }
} while(loginresult != -1);
}
@@ -241,6 +249,7 @@ int server_loop(int server)
if(recvresult == 0 || (recvresult == -1 && errno != EAGAIN && errno != EWOULDBLOCK) || get_user_state(user) == USER_STATE_TIMED_OUT)
{
// Logout User
+ if (--users == 0) change_blocking_mode(server, 0);
logout_user(user);
}
@@ -285,6 +294,7 @@ int server_loop(int server)
printf("Invalid Opcode 0x%02X in Waiting State from %u.%u.%u.%u.\n", user->rx[0], ip[0], ip[1], ip[2], ip[3]);
// Logout User
+ if (--users == 0) change_blocking_mode(server, 0);
logout_user(user);
}
}
@@ -369,6 +379,7 @@ int server_loop(int server)
printf("Invalid Opcode 0x%02X in Logged-In State from %s (MAC: %02X:%02X:%02X:%02X:%02X:%02X - IP: %u.%u.%u.%u).\n", user->rx[0], (char *)user->resolver.name.data, user->resolver.mac.data[0], user->resolver.mac.data[1], user->resolver.mac.data[2], user->resolver.mac.data[3], user->resolver.mac.data[4], user->resolver.mac.data[5], ip[0], ip[1], ip[2], ip[3]);
// Logout User
+ if (--users == 0) change_blocking_mode(server, 0);
logout_user(user);
}
}
--- a/src/user.c
+++ b/src/user.c
@@ -25,6 +25,8 @@
#include <status.h>
#include <config.h>
#include <sqlite3.h>
+#include <unistd.h>
+#include <sys/socket.h>
// User Count
uint32_t _db_user_count = 0;
Increasing the loop delay will only slowdown the response time.
What you need to do is to use blocking socket and use select
to detect data availability/readability and respond to it, since select
will blocks the thread more efficiently than sleep
, but you will probably need to use sleep
for about 1~10ms too on each loop, in case there are a lot of incoming data (ie. if you have a lot of active players) making select
unable to block the thread until there is no data left (where sleep
will let your CPU to rest a bit during high traffic but also gives extra response delay).
PS: as i remembered select
may have different behavior/returned error code on different platform, so make sure you've tested it on all platform if the changes are for PPSSPP built-in server (probably only need to test Windows, Linux, and MacOS)
PPS: there is also poll/epoll
that are recommended for modern app, but not all platforms support it, thus will also need to use select
as a fallback for unsupported platforms.
Well I'm not a network engineer so in the end I decided to just leave accept
blocking when no users are logged in, non-blocking as soon as somebody joins (change_blocking_mode(server,1)
) and then blocking again once all players left (change_blocking_mode(server,0)
). I'm sure you'll come up with something less awful though ;)
Well since it's running on it's own thread it will probably safe to use blocking mode, i don't remembered much why it use non-blocking mode tho :) may be it won't be able to respond for another player's communication while blocking the thread waiting for one player's data.