/src/openvswitch/lib/cooperative-multitasking.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2024 Canonical Ltd. |
3 | | * |
4 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | | * you may not use this file except in compliance with the License. |
6 | | * You may obtain a copy of the License at: |
7 | | * |
8 | | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | | * |
10 | | * Unless required by applicable law or agreed to in writing, software |
11 | | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | | * See the License for the specific language governing permissions and |
14 | | * limitations under the License. |
15 | | */ |
16 | | |
17 | | #include <config.h> |
18 | | |
19 | | #include "backtrace.h" |
20 | | #include "cooperative-multitasking-private.h" |
21 | | #include "cooperative-multitasking.h" |
22 | | #include "hash.h" |
23 | | #include "openvswitch/hmap.h" |
24 | | #include "openvswitch/vlog.h" |
25 | | #include "timeval.h" |
26 | | |
27 | | VLOG_DEFINE_THIS_MODULE(cooperative_multitasking); |
28 | | |
29 | | struct hmap cooperative_multitasking_callbacks = HMAP_INITIALIZER( |
30 | | &cooperative_multitasking_callbacks); |
31 | | |
32 | | /* Free any data allocated by calls to cooperative_multitasking_set(). */ |
33 | | void |
34 | | cooperative_multitasking_destroy(void) |
35 | 0 | { |
36 | 0 | struct cm_entry *cm_entry; |
37 | 0 | HMAP_FOR_EACH_SAFE (cm_entry, node, &cooperative_multitasking_callbacks) { |
38 | 0 | hmap_remove(&cooperative_multitasking_callbacks, &cm_entry->node); |
39 | 0 | free(cm_entry); |
40 | 0 | } |
41 | 0 | } |
42 | | |
43 | | /* Set/update callback as identified by 'cb' and 'arg'. |
44 | | * |
45 | | * 'name' is used for logging events related to this callback. |
46 | | * |
47 | | * The value for 'last_run' must be updated each time the callback is run. |
48 | | * |
49 | | * Updating the value for 'threshold' may be necessary as a consequence of |
50 | | * change in runtime configuration or requirements of the part of the program |
51 | | * serviced by the callback. |
52 | | * |
53 | | * Providing a value of 0 for 'last_run' or 'threshold' will leave the stored |
54 | | * value untouched. */ |
55 | | void |
56 | | cooperative_multitasking_set(void (*cb)(void *), void *arg, |
57 | | long long int last_run, long long int threshold, |
58 | | const char *name) |
59 | 0 | { |
60 | 0 | struct cm_entry *cm_entry; |
61 | |
|
62 | 0 | HMAP_FOR_EACH_WITH_HASH (cm_entry, node, hash_pointer((void *) cb, 0), |
63 | 0 | &cooperative_multitasking_callbacks) { |
64 | 0 | if (cm_entry->cb == cb && cm_entry->arg == arg) { |
65 | 0 | if (last_run) { |
66 | 0 | cm_entry->last_run = last_run; |
67 | 0 | } |
68 | |
|
69 | 0 | if (threshold) { |
70 | 0 | cm_entry->threshold = threshold; |
71 | 0 | } |
72 | 0 | return; |
73 | 0 | } |
74 | 0 | } |
75 | | |
76 | 0 | cm_entry = xzalloc(sizeof *cm_entry); |
77 | 0 | cm_entry->cb = cb; |
78 | 0 | cm_entry->arg = arg; |
79 | 0 | cm_entry->threshold = threshold; |
80 | 0 | cm_entry->last_run = last_run ? last_run : time_msec(); |
81 | 0 | cm_entry->name = name; |
82 | |
|
83 | 0 | hmap_insert(&cooperative_multitasking_callbacks, |
84 | 0 | &cm_entry->node, hash_pointer((void *) cm_entry->cb, 0)); |
85 | 0 | } |
86 | | |
87 | | /* Remove callback identified by 'cb' and 'arg'. */ |
88 | | void |
89 | | cooperative_multitasking_remove(void (*cb)(void *), void *arg) |
90 | 0 | { |
91 | 0 | struct cm_entry *cm_entry; |
92 | |
|
93 | 0 | HMAP_FOR_EACH_WITH_HASH (cm_entry, node, hash_pointer((void *) cb, 0), |
94 | 0 | &cooperative_multitasking_callbacks) { |
95 | 0 | if (cm_entry->cb == cb && cm_entry->arg == arg) { |
96 | 0 | hmap_remove(&cooperative_multitasking_callbacks, &cm_entry->node); |
97 | 0 | free(cm_entry); |
98 | 0 | return; |
99 | 0 | } |
100 | 0 | } |
101 | 0 | } |
102 | | |
103 | | static void |
104 | | cooperative_multitasking_yield_at__(const char *source_location) |
105 | 0 | { |
106 | 0 | long long int start = time_msec(); |
107 | 0 | struct cm_entry *cm_entry; |
108 | 0 | long long int elapsed; |
109 | 0 | bool warn; |
110 | |
|
111 | 0 | HMAP_FOR_EACH (cm_entry, node, &cooperative_multitasking_callbacks) { |
112 | 0 | elapsed = time_msec() - cm_entry->last_run; |
113 | |
|
114 | 0 | if (elapsed >= cm_entry->threshold) { |
115 | 0 | warn = elapsed - cm_entry->threshold > cm_entry->threshold / 8; |
116 | |
|
117 | 0 | VLOG(warn ? VLL_WARN : VLL_DBG, "%s: yield for %s(%p): " |
118 | 0 | "elapsed(%lld) >= threshold(%lld), overrun: %lld", |
119 | 0 | source_location, cm_entry->name, cm_entry->arg, elapsed, |
120 | 0 | cm_entry->threshold, elapsed - cm_entry->threshold); |
121 | |
|
122 | 0 | if (warn && VLOG_IS_DBG_ENABLED()) { |
123 | 0 | log_backtrace(); |
124 | 0 | } |
125 | |
|
126 | 0 | (*cm_entry->cb)(cm_entry->arg); |
127 | 0 | } |
128 | 0 | } |
129 | |
|
130 | 0 | elapsed = time_msec() - start; |
131 | 0 | if (elapsed > 1000) { |
132 | 0 | VLOG_WARN("Unreasonably long %lldms runtime for callbacks.", elapsed); |
133 | 0 | } |
134 | 0 | } |
135 | | |
136 | | /* Iterate over registered callbacks and execute callbacks as demanded by the |
137 | | * recorded time threshold. */ |
138 | | void |
139 | | cooperative_multitasking_yield_at(const char *source_location) |
140 | 0 | { |
141 | 0 | static bool yield_in_progress = false; |
142 | |
|
143 | 0 | if (yield_in_progress) { |
144 | 0 | VLOG_ERR_ONCE("Nested yield avoided, this is a bug! " |
145 | 0 | "Enable debug logging for more details."); |
146 | 0 | if (VLOG_IS_DBG_ENABLED()) { |
147 | 0 | VLOG_DBG("%s: nested yield.", source_location); |
148 | 0 | log_backtrace(); |
149 | 0 | } |
150 | 0 | return; |
151 | 0 | } |
152 | 0 | yield_in_progress = true; |
153 | |
|
154 | 0 | cooperative_multitasking_yield_at__(source_location); |
155 | |
|
156 | 0 | yield_in_progress = false; |
157 | 0 | } |