|
12 | 12 |
|
13 | 13 | """Functions to address filesystem calls, particularly sysfs."""
|
14 | 14 |
|
| 15 | +import functools |
15 | 16 | import os
|
| 17 | +import time |
16 | 18 |
|
17 | 19 | from oslo_log import log as logging
|
18 | 20 |
|
19 | 21 | from nova import exception
|
20 | 22 |
|
21 | 23 | LOG = logging.getLogger(__name__)
|
| 24 | +SYS = '/sys' |
| 25 | +RETRY_LIMIT = 5 |
22 | 26 |
|
23 | 27 |
|
24 |
| -SYS = '/sys' |
| 28 | +# a retry decorator to handle EBUSY |
| 29 | +def retry_if_busy(func): |
| 30 | + """Decorator to retry a function if it raises DeviceBusy. |
| 31 | +
|
| 32 | + This decorator will retry the function RETRY_LIMIT=5 times if it raises |
| 33 | + DeviceBusy. It will sleep for 1 second on the first retry, 2 seconds on |
| 34 | + the second retry, and so on, up to RETRY_LIMIT seconds. If the function |
| 35 | + still raises DeviceBusy after RETRY_LIMIT retries, the exception will be |
| 36 | + raised. |
| 37 | + """ |
25 | 38 |
|
| 39 | + @functools.wraps(func) |
| 40 | + def wrapper(*args, **kwargs): |
| 41 | + for i in range(RETRY_LIMIT): |
| 42 | + try: |
| 43 | + return func(*args, **kwargs) |
| 44 | + except exception.DeviceBusy as e: |
| 45 | + # if we have retried RETRY_LIMIT times, raise the exception |
| 46 | + # otherwise, sleep and retry, i is 0-based so we need |
| 47 | + # to add 1 to it |
| 48 | + count = i + 1 |
| 49 | + if count < RETRY_LIMIT: |
| 50 | + LOG.debug( |
| 51 | + f"File {e.kwargs['file_path']} is busy, " |
| 52 | + f"sleeping {count} seconds before retrying") |
| 53 | + time.sleep(count) |
| 54 | + continue |
| 55 | + raise |
| 56 | + return wrapper |
26 | 57 |
|
27 | 58 | # NOTE(bauzas): this method is deliberately not wrapped in a privsep entrypoint
|
| 59 | + |
| 60 | + |
| 61 | +@retry_if_busy |
28 | 62 | def read_sys(path: str) -> str:
|
29 | 63 | """Reads the content of a file in the sys filesystem.
|
30 | 64 |
|
31 | 65 | :param path: relative or absolute. If relative, will be prefixed by /sys.
|
32 | 66 | :returns: contents of that file.
|
33 | 67 | :raises: nova.exception.FileNotFound if we can't read that file.
|
| 68 | + :raises: nova.exception.DeviceBusy if the file is busy. |
34 | 69 | """
|
35 | 70 | try:
|
36 | 71 | # The path can be absolute with a /sys prefix but that's fine.
|
37 | 72 | with open(os.path.join(SYS, path), mode='r') as data:
|
38 | 73 | return data.read()
|
39 |
| - except (OSError, ValueError) as exc: |
| 74 | + except OSError as exc: |
| 75 | + # errno 16 is EBUSY, which means the file is busy. |
| 76 | + if exc.errno == 16: |
| 77 | + raise exception.DeviceBusy(file_path=path) from exc |
| 78 | + # convert permission denied to file not found |
| 79 | + raise exception.FileNotFound(file_path=path) from exc |
| 80 | + except ValueError as exc: |
40 | 81 | raise exception.FileNotFound(file_path=path) from exc
|
41 | 82 |
|
42 | 83 |
|
43 | 84 | # NOTE(bauzas): this method is deliberately not wrapped in a privsep entrypoint
|
44 | 85 | # In order to correctly use it, you need to decorate the caller with a specific
|
45 | 86 | # privsep entrypoint.
|
| 87 | +@retry_if_busy |
46 | 88 | def write_sys(path: str, data: str) -> None:
|
47 | 89 | """Writes the content of a file in the sys filesystem with data.
|
48 | 90 |
|
49 | 91 | :param path: relative or absolute. If relative, will be prefixed by /sys.
|
50 | 92 | :param data: the data to write.
|
51 | 93 | :returns: contents of that file.
|
52 | 94 | :raises: nova.exception.FileNotFound if we can't write that file.
|
| 95 | + :raises: nova.exception.DeviceBusy if the file is busy. |
53 | 96 | """
|
54 | 97 | try:
|
55 | 98 | # The path can be absolute with a /sys prefix but that's fine.
|
56 | 99 | with open(os.path.join(SYS, path), mode='w') as fd:
|
57 | 100 | fd.write(data)
|
58 |
| - except (OSError, ValueError) as exc: |
| 101 | + except OSError as exc: |
| 102 | + # errno 16 is EBUSY, which means the file is busy. |
| 103 | + if exc.errno == 16: |
| 104 | + raise exception.DeviceBusy(file_path=path) from exc |
| 105 | + # convert permission denied to file not found |
| 106 | + raise exception.FileNotFound(file_path=path) from exc |
| 107 | + except ValueError as exc: |
59 | 108 | raise exception.FileNotFound(file_path=path) from exc
|
0 commit comments