Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-99127: Port syslog module to use module state. #99128

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

corona10
Copy link
Member

@corona10 corona10 commented Nov 5, 2022

@corona10
Copy link
Member Author

corona10 commented Nov 5, 2022

(.oss) ➜  cpython git:(gh-99127) ✗ ./python.exe -m test test_syslog -R 3:3
Raised RLIMIT_NOFILE: 256 -> 1024
0:00:00 load avg: 3.65 Run tests sequentially
0:00:00 load avg: 3.65 [1/1] test_syslog
beginning 6 repetitions
123456
......

== Tests result: SUCCESS ==

1 test OK.

Total duration: 704 ms
Tests result: SUCCESS

@corona10 corona10 closed this Nov 5, 2022
@corona10 corona10 deleted the gh-99127 branch Nov 5, 2022
Copy link
Contributor

@ronaldoussoren ronaldoussoren left a comment

Something to consider: what happens when multiple sub interpreters use the syslog extension. The current implementation always calls openlog(3) at least once before calling syslog(3), see the check in syslog_syslog_impl.

That results in interference between sub interpreters when in one of them the python code explicitly calls syslog.openlog and the other doesn't.

IMHO the check in syslog_syslog_impl is not necessary because it results in a call to openlog(3) with parameters that match the default behaviour of the syslog library. Removing it is a fairly minor behaviour change though (syslog.syslog will currently override any calls to openlog that were done in C before the first call to syslog.syslog and will stop doing that when removing the check in syslog_syslog_impl).

_syslog_clear(PyObject *module)
{
_syslog_state *state = get_syslog_state(module);
Py_CLEAR(state->S_ident_o);
Copy link
Contributor

@ronaldoussoren ronaldoussoren Nov 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementations of openlog(3) might just store the "ident" argument (which is why S_ident_o exists). On such implementations the Py_CLEAR call results into use-after-free when syslog() is called from C code after this point.

Probably best to call closelog(3) with some default arguments before clearing.

@corona10 corona10 restored the gh-99127 branch Nov 5, 2022
@corona10 corona10 reopened this Nov 5, 2022
@corona10
Copy link
Member Author

corona10 commented Nov 5, 2022

Something to consider: what happens when multiple sub interpreters use the syslog extension. The current implementation always calls openlog(3) at least once before calling syslog(3), see the check in syslog_syslog_impl.

Thanks that's why I got a headache which makes me close the issue but thanks to suggest the solution.

IMHO the check in syslog_syslog_impl is not necessary because it results in a call to openlog(3) with parameters that match the default behaviour of the syslog library.

Yeah, IIUC we should call openlog(3) anyway while calling syslog.syslog right?
Please let me know if I understood it wrongly.

@corona10 corona10 requested a review from ronaldoussoren Nov 5, 2022
@corona10
Copy link
Member Author

corona10 commented Nov 5, 2022

Yeah, IIUC we should call openlog(3) anyway while calling syslog.syslog right?

Or did you intended implicit call of openlog(3) while calling syslog(3)?

@corona10 corona10 requested a review from vstinner Nov 6, 2022
@corona10 corona10 added the 🔨 test-with-buildbots Test the PR with the buildbot fleet and report in the status section label Nov 6, 2022
@bedevere-bot
Copy link

bedevere-bot commented Nov 6, 2022

🤖 New build scheduled with the buildbot fleet by @corona10 for commit 952ff46 🤖

If you want to schedule another build, you need to add the "🔨 test-with-buildbots" label again.

@bedevere-bot bedevere-bot removed the 🔨 test-with-buildbots Test the PR with the buildbot fleet and report in the status section label Nov 6, 2022
@corona10
Copy link
Member Author

corona10 commented Nov 6, 2022

Failure tests from buildbot/PPC64LE Fedora were not related to this change.

Modules/syslogmodule.c Outdated Show resolved Hide resolved
static void
_syslog_free(void *module)
{
_syslog_clear((PyObject *)module);
Copy link
Contributor

@kumaraditya303 kumaraditya303 Nov 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_syslog_clear((PyObject *)module);
(void)_syslog_clear((PyObject *)module);

Copy link
Member Author

@corona10 corona10 Nov 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why casting is needed at this moment?

Copy link
Contributor

@kumaraditya303 kumaraditya303 Nov 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some compilers like clang complain if the returned value is ignored and it is best practice to add cast to ignore the returned value.

Copy link
Member Author

@corona10 corona10 Nov 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some compilers like clang complain if the returned value is ignored

Please let me know which clang version emits the warning this case.
FYI I am using clang 14.

Copy link
Contributor

@kumaraditya303 kumaraditya303 Nov 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please let me know which clang version emits the warning this case.

I don't remember the version and the compiler option since I use gcc on linux not clang. There are occurrences of this in stdlib like

(void)partial_clear(pto);

Copy link
Member Author

@corona10 corona10 Nov 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah but some code doesn't use the casting also.

scanner_clear((PyScannerObject *)self);

But let's follow the recent convention, I can't find related issues about the casting.

Copy link
Contributor

@ronaldoussoren ronaldoussoren Nov 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Casting should not be necessary here and is just clutter. Clang and GCC can warn about ignored return values, but that's only when APIs are marked with a warn_unused_result attribute (which at least some libraries use to mark functions where the result is an error indicator that must be checked).

Modules/syslogmodule.c Outdated Show resolved Hide resolved
@erlend-aasland erlend-aasland removed their request for review Nov 6, 2022
@erlend-aasland
Copy link
Contributor

erlend-aasland commented Nov 6, 2022

Unfortunately I don't have time to review this right now. Maybe @serhiy-storchaka can have a look? He recently did some work on this module.

@ronaldoussoren
Copy link
Contributor

ronaldoussoren commented Nov 6, 2022

Yeah, IIUC we should call openlog(3) anyway while calling syslog.syslog right?
Please let me know if I understood it wrongly.

At the C level calling openlog(3) before calling syslog(3) is not necessary, although the spec is not entirely clear about this. The linux manual page does clearly mention that openlog(3) is optional, and matches what I remember from older Unix systems.

The call to openlog in syslog_syslog_impl basically resets the default system state (use the process name for the ident and log using the LOG_USER facility). Leaving that out should therefore be harmless, but this is technically a user-visible change of behaviour:

  • C code calls openlog(...) with same parameters
  • Python code calls syslog.syslog

Currently the Python code resets the values set in the first step, with my proposal it would no longer do this. I'd consider this a good change, but there's bound to be someone affected by this.

Copy link
Member

@vstinner vstinner left a comment

The stateless syslog C API doesn't seem to be designed to be consumed by multiple interpreters. I'm not sure that making S_ident_o and S_log_open global varibles per interpreter is safe.

static char S_log_open = 0;
typedef struct {
PyObject *S_ident_o; /* identifier, held by openlog() */
char S_log_open;
Copy link
Member

@vstinner vstinner Nov 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the S_ prefix stand for? Does it mean "state"? Maybe remove it?

static char S_log_open = 0;
typedef struct {
PyObject *S_ident_o; /* identifier, held by openlog() */
char S_log_open;
Copy link
Member

@vstinner vstinner Nov 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please add a comment to explain the purpose of the S_log_open flag? I understand that it's set to 1 after openlog() is called.

@@ -162,8 +170,9 @@ syslog_openlog_impl(PyObject *module, PyObject *ident, long logopt,
}

openlog(ident_str, logopt, facility);
Copy link
Member

@vstinner vstinner Nov 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This API is stateless. What happens if interpreter A calls openlog("idA", options_a, LONG_USER) and interpreter B calls openlog("idB", options_b, LOG_INFO)?

I'm not sure if it's a good idea to make the global S_log_open flag per-interpreter. See for example the recent crash on macOS: #98178. Same question for S_ident_o.

If you're confident that safe, please add a comment explaining the behavior, who owns the memory, etc.

Copy link
Member

@ericsnowcurrently ericsnowcurrently Nov 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is safe, but the behavior might be unexpected. The only issue of safety would be how POSIX syslog keeps a pointer to "ident" that must not be deallocated before closelog() is called. I don't see where that would be a problem.

That said, moving the data to per-interpreter module state may lead to a slight change in behavior. Again, though, using syslog with multiple interpreters is already wierd.

{
_syslog_state *state = get_syslog_state(module);
if (state->S_log_open) {
closelog();
Copy link
Member

@vstinner vstinner Nov 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if interpreter A calls closelog() and interpreter B (same process!) still uses the Python syslog module, so after closelog() has been called?

Copy link
Member

@ericsnowcurrently ericsnowcurrently Nov 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

POSIX syslog will transparently call openlog() with default values.

@bedevere-bot
Copy link

bedevere-bot commented Nov 7, 2022

When you're done making the requested changes, leave the comment: I have made the requested changes; please review again.

And if you don't make the requested changes, you will be poked with soft cushions!

@ericsnowcurrently
Copy link
Member

ericsnowcurrently commented Nov 7, 2022

I've posted to the issue about interpreters interfering with each other (which is not new with this PR): #99127 (comment).

@vstinner
Copy link
Member

vstinner commented Nov 7, 2022

For the specific case of sub-interpreters, maybe sub-interpreters must not be allowed to call openlog() and closelog()?

But for the simple case of a single intereterpreter but loading the C extensions twice, there is a similar issue.

This problem reminds me the complicated case of the posix/nt extension being converted to multi-phase init. I got a very complicated issue about Python exit. Some applications started to crash. Oops. Python os.environ[key] = value stored a copy of "key=value" string... until Python exit. But if the application still runs after Python shutdown, reading the environment could lead to reading freed memory which leaded to a crash! See:

The fix? Let the C library handles memory for us: use setenv() function, rather than putenv()!

For me, the Python syslog module should be a thin wrapper to a glue between Python and the underlying C library: have something which handles openlog() / closelog(), keep memory around, and only frees this memory when the last interpreter is deleted. Maybe it can be a shared state with a reference counter?

  • syslog instance 1 calls openlog(): copy arguments in heap memory
  • syslog instance 2 calls openlog(): free old memory, copy new arguments
  • ...
  • last syslog instance exits: call closelog(), release memory

For me, it sounds weird and dangerous to still use closelog() in another syslog instance which remains alive. Maybe it works on some operating systems. But again, see the recent crash on macOS.

@vstinner
Copy link
Member

vstinner commented Nov 7, 2022

For example, the Python socket.socket module has an _io_refs reference counter: it's used to decide when sock.close() will really close the underlying "raw" socket (C _socket.socket module).

@ericsnowcurrently
Copy link
Member

ericsnowcurrently commented Nov 8, 2022

Yeah, it is usually best to restrict management of global resources to the main interpreter. I'd say the syslog API counts as a global resource, so I'd be good with syslog.openlog() and syslog.closelog() only working in the main interpreter. Maybe also disallow syslog.syslog() in a subinterpreter if openlog() hasn't been called yet (or maybe not).

@ericsnowcurrently
Copy link
Member

ericsnowcurrently commented Nov 8, 2022

FYI, it looks like logging.handlers.SysLogHandler doesn't use the syslog module, so that shouldn't factor in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants