286b0d1f2
[vpp.git] /
1 /* SPDX-License-Identifier: Apache-2.0
2  * Copyright (c) 2025 Cisco Systems, Inc.
3  */
4
5 #include <vlib/vlib.h>
6 #include <vlib/unix/unix.h>
7
8 #include <sys/epoll.h>
9 #include <sys/eventfd.h>
10 #include <limits.h>
11
12 VLIB_REGISTER_LOG_CLASS (vlib_file_log, static) = {
13   .class_name = "vlib",
14   .subclass_name = "file",
15 };
16
17 #define log_debug(fmt, ...)                                                   \
18   vlib_log_debug (vlib_file_log.class, fmt, __VA_ARGS__)
19 #define log_warn(fmt, ...)                                                    \
20   vlib_log_warn (vlib_file_log.class, fmt, __VA_ARGS__)
21 #define log_err(fmt, ...) vlib_log_err (vlib_file_log.class, fmt, __VA_ARGS__)
22
23 clib_file_main_t file_main;
24
25 static void
26 vlib_file_update (clib_file_t *f, clib_file_update_type_t update_type)
27 {
28   vlib_main_t *vm = vlib_get_main_by_index (f->polling_thread_index);
29   int op = -1, add_del = 0;
30
31   struct epoll_event e = {
32     .events = EPOLLIN,
33     .data.ptr = f,
34   };
35
36   if (f->flags & UNIX_FILE_DATA_AVAILABLE_TO_WRITE)
37     e.events |= EPOLLOUT;
38   if (f->flags & UNIX_FILE_EVENT_EDGE_TRIGGERED)
39     e.events |= EPOLLET;
40
41   switch (update_type)
42     {
43     case UNIX_FILE_UPDATE_ADD:
44       op = EPOLL_CTL_ADD;
45       add_del = 1;
46       break;
47
48     case UNIX_FILE_UPDATE_MODIFY:
49       op = EPOLL_CTL_MOD;
50       break;
51
52     case UNIX_FILE_UPDATE_DELETE:
53       op = EPOLL_CTL_DEL;
54       add_del = -1;
55       break;
56
57     default:
58       log_err ("%s: unknown update_type %d", __func__, update_type);
59       return;
60     }
61
62   if (epoll_ctl (vm->epoll_fd, op, (int) f->file_descriptor, &e) < 0)
63     {
64       log_err ("%s: epoll_ctl() failed, errno %d", __func__, errno);
65       return;
66     }
67
68   vm->n_epoll_fds += add_del;
69 }
70
71 static clib_error_t *
72 wake_read_fn (struct clib_file *f)
73 {
74   u64 val, __clib_unused rv;
75   rv = read ((int) f->file_descriptor, &val, sizeof (u64));
76   return 0;
77 }
78
79 void
80 vlib_file_poll_init (vlib_main_t *vm)
81 {
82   vm->epoll_fd = epoll_create (1);
83
84   if (vm->epoll_fd < 0)
85     clib_panic ("failed to initialize epoll for thread %u", vm->thread_index);
86
87   vm->wakeup_fd = eventfd (0, EFD_NONBLOCK);
88
89   if (vm->wakeup_fd < 0)
90     clib_panic ("failed to initialize wakeup event for thread %u",
91                 vm->thread_index);
92
93   if (!file_main.file_update)
94     file_main.file_update = vlib_file_update;
95
96   clib_file_add (&file_main, &(clib_file_t){
97                                .polling_thread_index = vm->thread_index,
98                                .file_descriptor = vm->wakeup_fd,
99                                .description = format (0, "wakeup thread %u",
100                                                       vm->thread_index),
101                                .read_function = wake_read_fn,
102                              });
103 }
104
105 void
106 vlib_file_poll (vlib_main_t *vm)
107 {
108   vlib_node_main_t *nm = &vm->node_main;
109   unix_main_t *um = &unix_main;
110   struct epoll_event *e, epoll_events[16];
111   int n_fds_ready;
112   int is_main = (vm->thread_index == 0);
113   int timeout_ms = 0, max_timeout_ms = 10;
114   u32 ticks;
115
116   /*
117    * If we've been asked for a fixed-sleep between main loop polls,
118    * do so right away.
119    */
120   if (PREDICT_FALSE (is_main && um->poll_sleep_usec))
121     {
122       struct timespec ts, tsrem;
123       ts.tv_sec = 0;
124       ts.tv_nsec = 1000L * um->poll_sleep_usec;
125
126       while (nanosleep (&ts, &tsrem) < 0)
127         ts = tsrem;
128
129       goto epoll;
130     }
131
132   /* we are busy, skip some loops before polling again */
133   if (vlib_last_vectors_per_main_loop (vm) >= 2)
134     goto skip_loops;
135
136   /* at least one node is polling */
137   if (nm->input_node_counts_by_state[VLIB_NODE_STATE_POLLING])
138     goto skip_loops;
139
140   /* pending APIs in the queue */
141   if (is_main && vm->api_queue_nonempty)
142     goto skip_loops;
143
144   if (is_main == 0)
145     {
146       if (*vlib_worker_threads->wait_at_barrier)
147         goto epoll;
148
149       if (vlib_get_first_main ()->time_last_barrier_release + 0.5 >=
150           vlib_time_now (vm))
151         goto skip_loops;
152     }
153
154   /* check for pending interrupts */
155   for (int nt = 0; nt < VLIB_N_NODE_TYPE; nt++)
156     if (nm->node_interrupts[nt] &&
157         clib_interrupt_is_any_pending (nm->node_interrupts[nt]))
158       goto epoll;
159
160   /* at this point we know that thread is going to sleep, so let's annonce
161    * to other threads that they need to wakeup us if they need our attention */
162   __atomic_store_n (&vm->thread_sleeps, 1, __ATOMIC_RELAXED);
163
164   ticks = vlib_tw_timer_first_expires_in_ticks (vm);
165
166   if (ticks != TW_SLOTS_PER_RING)
167     {
168       timeout_ms = (int) (ticks / ((u32) VLIB_TW_TICKS_PER_SECOND / 1000));
169       timeout_ms = clib_min (timeout_ms, max_timeout_ms);
170     }
171   else
172     timeout_ms = max_timeout_ms;
173
174   goto epoll;
175
176 skip_loops:
177   /* Don't come back for a respectable number of dispatch cycles */
178   vm->file_poll_skip_loops = 1024;
179
180 epoll:
181   n_fds_ready = epoll_wait (vm->epoll_fd, epoll_events,
182                             ARRAY_LEN (epoll_events), timeout_ms);
183
184   __atomic_store_n (&vm->thread_sleeps, 0, __ATOMIC_RELAXED);
185   __atomic_store_n (&vm->wakeup_pending, 0, __ATOMIC_RELAXED);
186
187   if (n_fds_ready < 0)
188     {
189       if (unix_error_is_fatal (errno))
190         vlib_panic_with_error (vm, clib_error_return_unix (0, "epoll_wait"));
191
192       /* non fatal error (e.g. EINTR). */
193       return;
194     }
195
196   vm->epoll_waits += 1;
197   vm->epoll_files_ready += n_fds_ready;
198
199   for (e = epoll_events; e < epoll_events + n_fds_ready; e++)
200     {
201       clib_file_t *f = e->data.ptr;
202       clib_error_t *err;
203
204       if (PREDICT_FALSE (!f->active))
205         {
206           foreach_int (flag, EPOLLIN, EPOLLOUT, EPOLLERR)
207             if (e->events & flag)
208               {
209                 const char *str[] = {
210                   [EPOLLIN] = "EPOLLIN",
211                   [EPOLLOUT] = "EPOLLOUT",
212                   [EPOLLERR] = "EPOLLERR",
213                 };
214                 log_debug ("epoll event %s dropped due to inactive file",
215                            str[flag]);
216               }
217           continue;
218         }
219       else if (PREDICT_TRUE (!(e->events & EPOLLERR)))
220         {
221           if (e->events & EPOLLIN)
222             {
223               f->read_events++;
224               err = f->read_function (f);
225               if (err)
226                 {
227                   log_err ("file read error: %U", format_clib_error, err);
228                   clib_error_free (err);
229                 }
230             }
231           if (e->events & EPOLLOUT)
232             {
233               f->write_events++;
234               err = f->write_function (f);
235               if (err)
236                 {
237                   log_err ("file write error: %U", format_clib_error, err);
238                   clib_error_free (err);
239                 }
240             }
241         }
242       else
243         {
244           if (f->error_function)
245             {
246               f->error_events++;
247               err = f->error_function (f);
248               if (err)
249                 {
250                   log_err ("file error: %U", format_clib_error, err);
251                   clib_error_free (err);
252                 }
253             }
254           else if (f->dont_close == 0)
255             close ((int) f->file_descriptor);
256         }
257     }
258
259   /* maximum epoll events received, there may be more ... */
260   if (n_fds_ready == ARRAY_LEN (epoll_events))
261     {
262       timeout_ms = 0;
263       goto epoll;
264     }
265
266   /* removing fd from epoll instance doesn't remove event from epoll queue
267    * so we need to be sure epoll queue is empty before freeing */
268   clib_file_free_deleted (&file_main, vm->thread_index);
269 }
270
271 static clib_error_t *
272 show_files (vlib_main_t *vm, unformat_input_t *input, vlib_cli_command_t *cmd)
273 {
274   clib_error_t *error = 0;
275   clib_file_main_t *fm = &file_main;
276   char path[PATH_MAX];
277   u8 *s = 0;
278
279   vlib_cli_output (vm, "%3s %6s %12s %12s %12s %-32s %s", "FD", "Thread",
280                    "Read", "Write", "Error", "File Name", "Description");
281
282   pool_foreach_pointer (f, fm->file_pool)
283     {
284       ssize_t rv;
285       s = format (s, "/proc/self/fd/%d%c", f->file_descriptor, 0);
286       rv = readlink ((char *) s, path, PATH_MAX - 1);
287
288       path[rv < 0 ? 0 : rv] = 0;
289
290       vlib_cli_output (vm, "%3d %6d %12d %12d %12d %-32s %v",
291                        f->file_descriptor, f->polling_thread_index,
292                        f->read_events, f->write_events, f->error_events, path,
293                        f->description);
294       vec_reset_length (s);
295     }
296   vec_free (s);
297
298   return error;
299 }
300
301 VLIB_CLI_COMMAND (cli_show_files, static) = {
302   .path = "show files",
303   .short_help = "Show files in use",
304   .function = show_files,
305 };