diff options
author | Sam Gross <colesbury@gmail.com> | 2023-11-14 10:47:46 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-11-14 10:47:46 (GMT) |
commit | 324531df909721978446d504186738a33ab03fd5 (patch) | |
tree | fb3959265c6530ad8049e7511b428334c1657ac5 /Tools/clinic | |
parent | 16055c160412544e2a49794aaf3aa70c584f843a (diff) | |
download | cpython-324531df909721978446d504186738a33ab03fd5.zip cpython-324531df909721978446d504186738a33ab03fd5.tar.gz cpython-324531df909721978446d504186738a33ab03fd5.tar.bz2 |
gh-111903: Add `@critical_section` directive to Argument Clinic. (#111904)
The `@critical_section` directive instructs Argument Clinic to generate calls
to `Py_BEGIN_CRITICAL_SECTION()` and `Py_END_CRITICAL_SECTION()` around the
bound function. In `--disable-gil` builds, these calls will lock and unlock
the `self` object. They are no-ops in the default build.
This is used in one place (`_io._Buffered.close`) as a demonstration.
Subsequent PRs will use it more widely in the `_io.Buffered` bindings.
Diffstat (limited to 'Tools/clinic')
-rwxr-xr-x | Tools/clinic/clinic.py | 32 |
1 files changed, 27 insertions, 5 deletions
diff --git a/Tools/clinic/clinic.py b/Tools/clinic/clinic.py index 2ea93e6..a205a51 100755 --- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -470,6 +470,10 @@ class CRenderData: # The C statements required to clean up after the impl call. self.cleanup: list[str] = [] + # The C statements to generate critical sections (per-object locking). + self.lock: list[str] = [] + self.unlock: list[str] = [] + class FormatCounterFormatter(string.Formatter): """ @@ -1109,7 +1113,8 @@ class CLanguage(Language): condition=include.condition) has_option_groups = parameters and (parameters[0].group or parameters[-1].group) - default_return_converter = f.return_converter.type == 'PyObject *' + simple_return = (f.return_converter.type == 'PyObject *' + and not f.critical_section) new_or_init = f.kind.new_or_init vararg: int | str = NO_VARARG @@ -1183,7 +1188,9 @@ class CLanguage(Language): """) + "\n" finale = normalize_snippet(""" {modifications} + {lock} {return_value} = {c_basename}_impl({impl_arguments}); + {unlock} {return_conversion} {post_parsing} @@ -1219,7 +1226,7 @@ class CLanguage(Language): flags = "METH_METHOD|METH_FASTCALL|METH_KEYWORDS" parser_prototype = self.PARSER_PROTOTYPE_DEF_CLASS - return_error = ('return NULL;' if default_return_converter + return_error = ('return NULL;' if simple_return else 'goto exit;') parser_code = [normalize_snippet(""" if (nargs) {{ @@ -1228,7 +1235,7 @@ class CLanguage(Language): }} """ % return_error, indent=4)] - if default_return_converter: + if simple_return: parser_definition = '\n'.join([ parser_prototype, '{{', @@ -1245,7 +1252,7 @@ class CLanguage(Language): converters[0].format_unit == 'O'): meth_o_prototype = self.METH_O_PROTOTYPE - if default_return_converter: + if simple_return: # maps perfectly to METH_O, doesn't need a return converter. # so we skip making a parse function # and call directly into the impl function. @@ -1858,6 +1865,10 @@ class CLanguage(Language): selfless = parameters[1:] assert isinstance(f_self.converter, self_converter), "No self parameter in " + repr(f.full_name) + "!" + if f.critical_section: + data.lock.append('Py_BEGIN_CRITICAL_SECTION({self_name});') + data.unlock.append('Py_END_CRITICAL_SECTION();') + last_group = 0 first_optional = len(selfless) positional = selfless and selfless[-1].is_positional_only() @@ -1937,6 +1948,8 @@ class CLanguage(Language): template_dict['post_parsing'] = format_escape("".join(data.post_parsing).rstrip()) template_dict['cleanup'] = format_escape("".join(data.cleanup)) template_dict['return_value'] = data.return_value + template_dict['lock'] = "\n".join(data.lock) + template_dict['unlock'] = "\n".join(data.unlock) # used by unpack tuple code generator unpack_min = first_optional @@ -1961,6 +1974,8 @@ class CLanguage(Language): modifications=template_dict['modifications'], post_parsing=template_dict['post_parsing'], cleanup=template_dict['cleanup'], + lock=template_dict['lock'], + unlock=template_dict['unlock'], ) # Only generate the "exit:" label @@ -2954,6 +2969,7 @@ class Function: # functions with optional groups because we can't represent # those accurately with inspect.Signature in 3.4. docstring_only: bool = False + critical_section: bool = False def __post_init__(self) -> None: self.parent = self.cls or self.module @@ -5108,6 +5124,7 @@ class DSLParser: coexist: bool parameter_continuation: str preserve_output: bool + critical_section: bool from_version_re = re.compile(r'([*/]) +\[from +(.+)\]') def __init__(self, clinic: Clinic) -> None: @@ -5142,6 +5159,7 @@ class DSLParser: self.forced_text_signature: str | None = None self.parameter_continuation = '' self.preserve_output = False + self.critical_section = False def directive_version(self, required: str) -> None: global version @@ -5270,6 +5288,9 @@ class DSLParser: fail("Can't set @classmethod, function is not a normal callable") self.kind = CLASS_METHOD + def at_critical_section(self) -> None: + self.critical_section = True + def at_staticmethod(self) -> None: if self.kind is not CALLABLE: fail("Can't set @staticmethod, function is not a normal callable") @@ -5492,7 +5513,8 @@ class DSLParser: return_converter = CReturnConverter() self.function = Function(name=function_name, full_name=full_name, module=module, cls=cls, c_basename=c_basename, - return_converter=return_converter, kind=self.kind, coexist=self.coexist) + return_converter=return_converter, kind=self.kind, coexist=self.coexist, + critical_section=self.critical_section) self.block.signatures.append(self.function) # insert a self converter automatically |