File: pyi_timing_thread.c

package info (click to toggle)
python-pyinstrument 5.1.2%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,672 kB
  • sloc: python: 6,907; ansic: 897; makefile: 46; sh: 26; javascript: 18
file content (152 lines) | stat: -rw-r--r-- 4,162 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#include "pyi_timing_thread.h"

#include <Python.h>
#include <time.h>
#include <float.h>

#include "pyi_floatclock.h"

static volatile double current_time = 0.0;

static PyThread_type_lock subscriber_lock = NULL;
static PyThread_type_lock update_lock = NULL;
static int thread_should_exit = 0;
static int thread_alive = 0;

// Structure to hold subscriptions
typedef struct Subscription {
    double interval;
    int id;
} Subscription;

#define MAX_SUBSCRIBERS 1000
static Subscription subscribers[MAX_SUBSCRIBERS];
static int subscriber_count = 0;

static double get_interval(double max_interval) {
    double min_interval = max_interval;

    for (int i = 0; i < subscriber_count; i++) {
        if (subscribers[i].interval < min_interval) {
            min_interval = subscribers[i].interval;
        }
    }

    return min_interval;
}

static void timing_thread(void* args) {
    while (!thread_should_exit) {
        double interval = get_interval(1.0);
        // sleep for the interval, or until we're woken up by a change
        PyLockStatus status = PyThread_acquire_lock_timed(
            update_lock,
            (PY_TIMEOUT_T)(interval * 1e6),
            0
        );
        if (status == PY_LOCK_ACQUIRED) {
            // rather than finishing the wait, another thread signaled a
            // change by releasing the lock. The lock was just for the sake of
            // the wakeup, so let's release it again.
            PyThread_release_lock(update_lock);
        }
        current_time = pyi_floatclock(PYI_FLOATCLOCK_DEFAULT);
    }
}

int pyi_timing_thread_subscribe(double desiredInterval) {
    if (subscriber_lock == NULL) {
        subscriber_lock = PyThread_allocate_lock();
    }
    if (update_lock == NULL) {
        update_lock = PyThread_allocate_lock();
    }

    PyThread_acquire_lock(subscriber_lock, WAIT_LOCK);

    if (!thread_alive) {
        PyThread_acquire_lock(update_lock, WAIT_LOCK);  // Initially hold the lock
        thread_should_exit = 0;
        PyThread_start_new_thread(timing_thread, NULL);
        thread_alive = 1;

        // initialise the current_time in case it's read immediately
        current_time = pyi_floatclock(PYI_FLOATCLOCK_DEFAULT);
    }

    int new_id = 0;

    // find an unused ID
    for (; new_id < MAX_SUBSCRIBERS; new_id++) {
        int already_exists = 0;
        for (int i = 0; i < subscriber_count; i++) {
            if (subscribers[i].id == new_id) {
                already_exists = 1;
                break;
            }
        }
        if (!already_exists) {
            break;
        }
    }
    if (new_id == MAX_SUBSCRIBERS) {
        // Too many subscribers
        PyThread_release_lock(subscriber_lock);
        return PYI_TIMING_THREAD_TOO_MANY_SUBSCRIBERS;
    }

    int index = subscriber_count;
    subscribers[index].id = new_id;
    subscribers[index].interval = desiredInterval;
    subscriber_count++;

    // signal a possible change in the interval
    PyThread_release_lock(update_lock);
    PyThread_acquire_lock(update_lock, WAIT_LOCK);

    PyThread_release_lock(subscriber_lock);
    return new_id;
}

int pyi_timing_thread_unsubscribe(int id) {
    PyThread_acquire_lock(subscriber_lock, WAIT_LOCK);

    int removals = 0;

    for (int i = 0; i < subscriber_count; i++) {
        if (subscribers[i].id == id) {
            // Removal: overwrite this one with with the last element and decrement count.
            subscribers[i] = subscribers[subscriber_count-1];
            subscriber_count--;
            removals++;
            break;
        }
    }

    // if the last subscriber was removed, stop the thread
    if (subscriber_count == 0) {
        thread_should_exit = 1;
        PyThread_release_lock(update_lock);
        thread_alive = 0;
    }

    PyThread_release_lock(subscriber_lock);

    if (removals == 0) {
        return PYI_TIMING_THREAD_NOT_SUBSCRIBED;
    } else {
        return 0;
    }
}

double pyi_timing_thread_get_time(void) {
    return current_time;
}

double pyi_timing_thread_get_interval(void) {
    if (thread_alive) {
        return get_interval(DBL_MAX);
    } else {
        return -1.0;
    }
}