From 0728db444fa3eff43018f911de26234e10131490 Mon Sep 17 00:00:00 2001 From: Maarten Lankhorst Date: Mon, 23 Mar 2015 09:14:29 +0100 Subject: [PATCH] rtkit: add SIGXCPU handling to wineserver This is dependent on getting the correct server_pid in the previous commit. Processes now forward SIGXCPU to wineserver, who will attempt to downgrade all threads that were set to realtime priority by avrt first, and if another SIGXCPU is received or none were found it will downgrade all realtime threads. Special-thanks-to: tizbac --- dlls/avrt/main.c | 2 +- dlls/ntdll/server.c | 94 +++++++++++++++++++++++++++++++++- libs/wine/loader.c | 10 ++-- server/main.c | 4 +- server/rtkit.c | 144 ++++++++++++++++++++++++++++++++++++++++++++++------ server/thread.c | 40 +++++++++++---- server/thread.h | 2 + 7 files changed, 261 insertions(+), 35 deletions(-) diff --git a/dlls/avrt/main.c b/dlls/avrt/main.c index aa6b95d5cd6..4e29abf9ddd 100644 --- a/dlls/avrt/main.c +++ b/dlls/avrt/main.c @@ -80,7 +80,7 @@ HANDLE WINAPI AvSetMmThreadCharacteristicsW(LPCWSTR TaskName, LPDWORD TaskIndex) SetLastError(ERROR_INVALID_HANDLE); return NULL; } - SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL-1); return (HANDLE)0x12345678; } diff --git a/dlls/ntdll/server.c b/dlls/ntdll/server.c index 356d6319f15..4775dece61a 100644 --- a/dlls/ntdll/server.c +++ b/dlls/ntdll/server.c @@ -83,6 +83,9 @@ #include "ntdll_misc.h" WINE_DEFAULT_DEBUG_CHANNEL(server); +WINE_DECLARE_DEBUG_CHANNEL(winediag); +WINE_DECLARE_DEBUG_CHANNEL(tid); +WINE_DECLARE_DEBUG_CHANNEL(timestamp); /* Some versions of glibc don't define this */ #ifndef SCM_RIGHTS @@ -1361,6 +1364,88 @@ static int get_unix_tid(void) } +#ifdef SIGXCPU +static int convert_tidtostr_r(char *s, unsigned hex) +{ + int i; + char *start = s; + + for (i = 0; i < 8; ++i) { + unsigned c = hex >> (28 - 4 * i); + + if (c || i >= 4) + { /* last 4 digits always printed */ + c &= 0xf; + + if (c < 10) + *s++ = '0' + c; + else + *s++ = 'a' + c - 10; + } + } + + *s++ = ':'; + return s - start; +} + +static int convert_stamptostr_r(char *s, unsigned stamp) +{ + unsigned high = stamp / 1000, low = stamp % 1000, i; + char *start = s; + + for (i = 1000000; i; i /= 10) { + if (high >= i) + *s++ = '0' + (high / i) % 10; + else if (i <= 100) + *s++ = ' '; + } + + *s++ = '.'; + + for (i = 100; i; i /= 10) + *s++ = '0' + (low / i) % 10; + + *s++ = ':'; + + return s - start; +} + + +static const char throttle_str[] = +"fixme:winediag:sigxcpu_handler realtime priority was throttled due to program exceeding time limit\n"; + +static void sigxcpu_handler( int sig ) +{ + char temp[16]; + int old_errno = errno, ret; + + if (server_pid > 0) + kill(server_pid, SIGXCPU); + else { + /* uh oh, somehow init failed to get server_pid */ + struct sched_param parm; + memset(&parm, 0, sizeof(parm)); + sched_setscheduler(0, SCHED_OTHER | SCHED_RESET_ON_FORK, &parm); + } + + if (FIXME_ON(winediag)) { + if (TRACE_ON(timestamp)) { + ret = convert_stamptostr_r(temp, NtGetTickCount()); + write(2, temp, ret); + } + + if (TRACE_ON(tid)) { + ret = convert_tidtostr_r(temp, GetCurrentThreadId()); + write(2, temp, ret); + } + + write(2, throttle_str, sizeof(throttle_str)-1); + } + + errno = old_errno; +} +#endif + /*********************************************************************** * server_init_process * @@ -1370,6 +1455,14 @@ void server_init_process(void) { obj_handle_t version; const char *env_socket = getenv( "WINESERVERSOCKET" ); +#ifdef SIGXCPU + struct sigaction sa; + + sa.sa_handler = sigxcpu_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction( SIGXCPU, &sa, NULL ); +#endif server_pid = -1; if (env_socket) @@ -1453,7 +1546,6 @@ NTSTATUS server_init_process_done(void) return status; } - /*********************************************************************** * server_init_thread * diff --git a/libs/wine/loader.c b/libs/wine/loader.c index b9c3fa07ab8..1644955f92e 100644 --- a/libs/wine/loader.c +++ b/libs/wine/loader.c @@ -933,10 +933,12 @@ static void set_rttime_limit(void) if (!getrlimit( RLIMIT_RTTIME, &rlimit )) { - /* 50 ms realtime, then 10 ms to undo realtime */ - if (rlimit.rlim_max > 150000000ULL) - rlimit.rlim_max = 150000000ULL; - rlimit.rlim_cur = rlimit.rlim_max - 10000000ULL; + /* 1000 ms maximum realtime before the first SIGXCPU, this will drop + * all realtime threads to normal priority. + */ + if (rlimit.rlim_max > 5000000) + rlimit.rlim_max = 5000000; + rlimit.rlim_cur = 1000000; setrlimit( RLIMIT_RTTIME, &rlimit ); } diff --git a/server/main.c b/server/main.c index 6c257b4676d..f78c0fb3015 100644 --- a/server/main.c +++ b/server/main.c @@ -39,7 +39,7 @@ #include "request.h" #include "wine/library.h" -extern int rtkit_make_realtime(pid_t process, pid_t thread, int priority); +extern int rtkit_make_realtime(struct thread *thread, pid_t tid, int priority); /* command-line options */ int debug_level = 0; @@ -148,7 +148,7 @@ int main( int argc, char *argv[] ) init_signals(); init_directories(); init_registry(); - rtkit_make_realtime(getpid(), syscall( SYS_gettid ), 2); + rtkit_make_realtime(NULL, syscall( SYS_gettid ), 3); main_loop(); return 0; } diff --git a/server/rtkit.c b/server/rtkit.c index a2121068b88..53e5ce4d61e 100644 --- a/server/rtkit.c +++ b/server/rtkit.c @@ -35,7 +35,11 @@ #include #include #include +#include +#include +#include #include "object.h" +#include "thread.h" #ifndef RLIMIT_RTTIME #define RLIMIT_RTTIME 15 @@ -53,6 +57,8 @@ FUNCPTR(dbus_message_unref); FUNCPTR(dbus_set_error_from_message); #undef FUNCPTR +static struct list rt_thread_list = LIST_INIT(rt_thread_list); + static int translate_error( unsigned tid, const char *name ) { if (!strcmp( name, DBUS_ERROR_NO_MEMORY )) @@ -85,6 +91,88 @@ static void init_dbus(void) #undef FUNCPTR } +#define MSG_SIGXCPU "wineserver: SIGXCPU called on wineserver from kernel, realtime priority removed!\n" + +static int sched_normal(struct thread *cur) +{ + int ret = 0; + + if (cur->unix_tid != -1) { + struct sched_param parm; + memset( &parm, 0, sizeof( parm ) ); + ret = sched_setscheduler(cur->unix_tid, SCHED_OTHER | SCHED_RESET_ON_FORK, &parm); + if (ret < 0) + ret = -errno; + } + + list_remove(&cur->rt_entry); + list_init(&cur->rt_entry); + cur->rt_prio = 0; + cur->priority = 0; + return ret; +} + +static void sigxcpu_handler(int sig, siginfo_t *si, void *ucontext) +{ + struct thread *cur, *tmp; + int found = 0; + int old_errno = errno; + + if (si->si_code & SI_KERNEL) { + struct sched_param parm; + memset( &parm, 0, sizeof( parm ) ); + + sched_setscheduler(syscall( SYS_gettid ), SCHED_OTHER | SCHED_RESET_ON_FORK, &parm); + + write(2, MSG_SIGXCPU, sizeof(MSG_SIGXCPU)-1); + goto restore_errno; + } + + LIST_FOR_EACH_ENTRY_SAFE(cur, tmp, &rt_thread_list, struct thread, rt_entry) + { + if (si->si_pid == cur->unix_pid && cur->rt_prio == 1) { + found = 1; + sched_normal(cur); + } + } + + if (!found) { + LIST_FOR_EACH_ENTRY_SAFE(cur, tmp, &rt_thread_list, struct thread, rt_entry) + { + if (si->si_pid == cur->unix_pid) + sched_normal(cur); + } + } + +restore_errno: + errno = old_errno; +} + +static void setup_rt(void) +{ + struct sigaction sa; + struct rlimit rlimit; + + sa.sa_sigaction = sigxcpu_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + sigaction(SIGXCPU, &sa, NULL); + + if (!getrlimit( RLIMIT_RTTIME, &rlimit )) + { + /* wineserver can run for 1.5 seconds continuously at realtime before + * it gets throttled down. At this point we probably hit a bug + * somewhere. + */ + if (rlimit.rlim_max > 2000000) + rlimit.rlim_max = 2000000; + if (rlimit.rlim_cur > 1500000) + rlimit.rlim_cur = 1500000; + + setrlimit( RLIMIT_RTTIME, &rlimit ); + } +} + static DBusConnection *get_dbus(void) { static DBusConnection *bus; @@ -96,16 +184,18 @@ static DBusConnection *get_dbus(void) pdbus_error_init( &error ); bus = pdbus_bus_get( DBUS_BUS_SYSTEM, &error ); + setup_rt(); return bus; } -int rtkit_make_realtime( pid_t process, pid_t thread, int priority ) +int rtkit_make_realtime( struct thread *thread, pid_t unix_tid, int priority ) { DBusConnection *bus; DBusMessage *m = NULL, *r = NULL; - dbus_uint64_t pid = process; - dbus_uint64_t tid = thread; + dbus_uint64_t pid = thread ? thread->unix_pid : getpid(); + dbus_uint64_t tid = unix_tid; dbus_uint32_t rtprio = priority; + sigset_t sigset; DBusError error; int ret; @@ -133,16 +223,29 @@ int rtkit_make_realtime( pid_t process, pid_t thread, int priority ) ret = -ENOMEM; goto out; } + + sigemptyset( &sigset ); + sigaddset( &sigset, SIGXCPU ); + sigprocmask( SIG_BLOCK, &sigset, NULL ); + r = pdbus_connection_send_with_reply_and_block( bus, m, -1, &error ); if (!r) { ret = translate_error( tid, error.name ); - goto out; + goto out_unblock; } if (pdbus_set_error_from_message( &error, r )) ret = translate_error( tid, error.name ); - else + else { ret = 0; + if (thread) { + if (list_empty(&thread->rt_entry)) + list_add_tail( &rt_thread_list, &thread->rt_entry ); + thread->rt_prio = rtprio; + } + } +out_unblock: + sigprocmask( SIG_UNBLOCK, &sigset, NULL ); out: if (m) pdbus_message_unref( m ); @@ -150,29 +253,38 @@ out: pdbus_message_unref( r ); pdbus_error_free( &error ); if (debug_level) - fprintf( stderr, "%04x: Setting realtime priority of %u returns %i\n", (int)tid, rtprio, ret ); + fprintf( stderr, "%04x: Setting realtime priority of %u returns %i %m\n", (int)tid, rtprio, ret ); return ret; } -int rtkit_undo_realtime( pid_t thread ) +int rtkit_undo_realtime( struct thread *thread ) { - struct sched_param parm; - int ret; - memset( &parm, 0, sizeof( parm ) ); - ret = sched_setscheduler( thread, SCHED_OTHER, &parm ); - if (ret < 0) - return -errno; - return ret; + sigset_t sigset; + int ret = 0; + + sigemptyset( &sigset ); + sigaddset( &sigset, SIGXCPU ); + sigprocmask( SIG_BLOCK, &sigset, NULL ); + + if (!list_empty(&thread->rt_entry)) + ret = sched_normal(thread); + + if (debug_level) + fprintf( stderr, "%04x: Removing realtime priority of %u returns %i %m\n", + (int)thread->unix_tid, thread->rt_prio, ret ); + + sigprocmask( SIG_UNBLOCK, &sigset, NULL ); + return ret < 0 ? -errno : 0; } #else -int rtkit_make_realtime( pid_t process, pid_t thread, int priority ) +int rtkit_make_realtime( struct thread *thread, pid_t unix_tid, int priority ) { return -ENOTSUP; } -int rtkit_undo_realtime( pid_t thread ) +int rtkit_undo_realtime( struct thread *thread ) { return -ENOTSUP; } diff --git a/server/thread.c b/server/thread.c index fbffffb0b09..194c6c37221 100644 --- a/server/thread.c +++ b/server/thread.c @@ -52,8 +52,8 @@ #include "user.h" #include "security.h" -extern int rtkit_make_realtime(pid_t process, pid_t thread, int priority); -extern int rtkit_undo_realtime(pid_t thread); +extern int rtkit_make_realtime(struct thread *thread, pid_t tid, int priority); +extern int rtkit_undo_realtime(struct thread *thread); #ifdef __i386__ static const unsigned int supported_cpus = CPU_FLAG(CPU_x86); @@ -204,6 +204,8 @@ static inline void init_thread_structure( struct thread *thread ) list_init( &thread->mutex_list ); list_init( &thread->system_apc ); list_init( &thread->user_apc ); + list_init( &thread->rt_entry ); + thread->rt_prio = 0; for (i = 0; i < MAX_INFLIGHT_FDS; i++) thread->inflight[i].server = thread->inflight[i].client = -1; @@ -278,6 +280,9 @@ static void cleanup_thread( struct thread *thread ) { int i; + thread->unix_tid = -1; + if (!list_empty(&thread->rt_entry)) + rtkit_undo_realtime(thread); clear_apc_queue( &thread->system_apc ); clear_apc_queue( &thread->user_apc ); free( thread->req_data ); @@ -473,6 +478,15 @@ affinity_t get_thread_affinity( struct thread *thread ) #define THREAD_PRIORITY_REALTIME_HIGHEST 6 #define THREAD_PRIORITY_REALTIME_LOWEST -7 +static int rtprio(int ntprio) +{ + if (ntprio == THREAD_PRIORITY_TIME_CRITICAL - 1) + return 1; + else if (ntprio == THREAD_PRIORITY_TIME_CRITICAL) + return 2; + return 0; +} + /* set all information about a thread */ static void set_thread_info( struct thread *thread, const struct set_thread_info_request *req ) @@ -488,17 +502,21 @@ static void set_thread_info( struct thread *thread, } if ((req->priority >= min && req->priority <= max) || req->priority == THREAD_PRIORITY_IDLE || + req->priority == THREAD_PRIORITY_TIME_CRITICAL - 1 || req->priority == THREAD_PRIORITY_TIME_CRITICAL) { + int newprio = rtprio(req->priority); if (thread->unix_tid == -1) - {} - else if (thread->priority == THREAD_PRIORITY_TIME_CRITICAL && - req->priority != THREAD_PRIORITY_TIME_CRITICAL) - rtkit_undo_realtime(thread->unix_tid); - else if (thread->priority != THREAD_PRIORITY_TIME_CRITICAL && - req->priority == THREAD_PRIORITY_TIME_CRITICAL) - rtkit_make_realtime(thread->unix_pid, thread->unix_tid, 1); - thread->priority = req->priority; + thread->rt_prio = newprio; + else if (thread->priority == THREAD_PRIORITY_TIME_CRITICAL && !newprio) + rtkit_undo_realtime(thread); + else if (thread->rt_prio != newprio) + rtkit_make_realtime(thread, thread->unix_tid, newprio); + + if (newprio) + thread->priority = THREAD_PRIORITY_TIME_CRITICAL; + else + thread->priority = req->priority; } else set_error( STATUS_INVALID_PARAMETER ); @@ -1334,7 +1352,7 @@ DECL_HANDLER(init_thread) /* Raced with SetThreadPriority */ if (current->priority == THREAD_PRIORITY_TIME_CRITICAL) - rtkit_make_realtime(current->unix_pid, current->unix_tid, 1); + rtkit_make_realtime(current, current->unix_tid, current->rt_prio); reply->pid = get_process_id( process ); reply->tid = get_thread_id( current ); diff --git a/server/thread.h b/server/thread.h index 282199149e6..87817214f54 100644 --- a/server/thread.h +++ b/server/thread.h @@ -88,6 +88,8 @@ struct thread timeout_t creation_time; /* Thread creation time */ timeout_t exit_time; /* Thread exit time */ struct token *token; /* security token associated with this thread */ + struct list rt_entry; /* entry for member in linked realtime list */ + int rt_prio; /* current realtime thread priority */ }; struct thread_snapshot -- 2.11.4.GIT