-
Notifications
You must be signed in to change notification settings - Fork 914
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
Force a write of all user-space buffered data for stdout in Formatter::print #1012
Conversation
Forcing a flush after every line of output results in a significant performance overhead when printing more information to the console. Therefore I don't think this can be merged as a default behavior. I could imagine an environment variable (like in Python using the env var |
So I read a little bit more about the problem. Just to be more exact, I observe this behaviour because the program has stdout as PIPE not TTY. On the same machine (android) when I run the same program from terminal, it works well. But in my usecase I cannot do it from terminal as I do not have one inside of Android application. Currently I print the output of my ros application to the log of Android, then I wanted to show it in the user GUI. So as I understand there is not possibility to setup TTY in my case. As it's written in the scripture:
Stdout without tty is fully-buffered, this is why I cannot see the output until it reaches certain threshold. For the similar case
So if
So it seems like in some cases people actually need line-buffered output for pipes, like the example of grep shows. So maybe as you proposed we should do environment variable which makes logs written to stdout flush on every line. I will do it then. |
@dirk-thomas I created an environment variable in the updated pull request. I have not yet finished it completely, there is something to do still, but just to get the general idea I think it's ok. Could you have a look that in principle it's ok or not and what would you change? What is not finished yet is correct parsing of "true" with lexical cast. |
Instead of explicitly flushing each line which will only affect the ROS logging messages I would consider changing the buffering of
Based on that the environment variable could be named Since the suggested behavior is not specific to |
Ok, I agree, I also thought about it at some point. In this case user will be able to force line buffering for any app built on top of ROS, even if it does not log everything with logger. About drawbacks, probably there are no drawbacks. We override stdout policy globally for both ROS and some application that uses it, but we do so only when user explicitly ask us, then probably it's ok. |
@dirk-thomas why did you write _IONBF? It stands for completely unbuffered stream. Should we make linebuffering instead _IOLBF? |
The snippet was just an example. Line buffered sounds indeed better. |
@dirk-thomas , implementation is clear except for one thing, which I am concerned now about. The documentation of setvbuf states http://man7.org/linux/man-pages/man3/setvbuf.3p.html that
So as I understand it means that no write to stdout should happen before
So it does not state that
In my view in could cause side effects in such situations. What do you think? |
I agree that this is a possibility. Trying to influence the buffering of a compiler executable is not as easy as for Python. I don't see a better approach though. You might want to try what happens in that case. |
@dirk-thomas I think it's implementation specific and undefined behaviour. |
@@ -425,6 +435,25 @@ void initialize() | |||
backend::function_notifyLoggerLevelsChanged = notifyLoggerLevelsChanged; | |||
backend::function_print = _print; | |||
|
|||
char* line_buffered = NULL; | |||
#ifdef _MSC_VER | |||
_dupenv_s(&line_buffered, NULL, "ROSCONSOLE_STDOUT_LINE_BUFFERED"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the Windows case you must free the allocated memory (line_buffered
) afterwards.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually please use get_environment_variable()
which encapsulates the logic already: https://github.com/ros/roscpp_core/blob/5820d0672cb9d5fcd1c8cb4a159d04ff20115ff3/cpp_common/include/ros/platform.h#L63
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
if (g_force_stdout_line_buffered && f == stdout) { | ||
int flushResult = fflush(f); | ||
if (flushResult != 0) { | ||
fprintf(stderr, "Failed to perform fflush on stdout, return code is %d\n", flushResult); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the flushing fails I don't think the repeated error message for every case is helpful. I would suggest to only print the error message for the first occurrence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -393,6 +396,13 @@ void Formatter::print(void* logger_handle, ::ros::console::Level level, const ch | |||
ss << COLOR_NORMAL; | |||
|
|||
fprintf(f, "%s\n", ss.str().c_str()); | |||
|
|||
if (g_force_stdout_line_buffered && f == stdout) { | |||
int flushResult = fflush(f); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please follow the style for variable names which is lowercase-underscore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
{ | ||
try | ||
{ | ||
g_force_stdout_line_buffered = boost::lexical_cast<bool>(std::string(line_buffered)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding the lexical cast. Similar existing checks don't bother with it and just check for a fixed value, e.g.:
ros_comm/clients/roscpp/src/libros/init.cpp
Lines 319 to 326 in 148178b
char* env_ipv6 = NULL; #ifdef _MSC_VER _dupenv_s(&env_ipv6, NULL, "ROS_IPV6"); #else env_ipv6 = getenv("ROS_IPV6"); #endif bool use_ipv6 = (env_ipv6 && strcmp(env_ipv6,"on") == 0); return ROS_IPV6 in os.environ and os.environ[ROS_IPV6] == 'on' ros_comm/tools/roslaunch/src/roslaunch/remoteprocess.py
Lines 106 to 107 in 148178b
override = os.environ.get('ROSLAUNCH_SSH_UNKNOWN', 0) if override == '1':
And in case the value doesn't match the expectation they silently apply the default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed lexical cast. In case the value does not equal 1, the default value 0 will be used. If the value provided is neither 0 nor 1, a warning is issued to notify user that something is wrong and he/she needs to check correctness of parameters.
} else { | ||
if (line_buffered != "0") { | ||
fprintf(stderr, "Warning: unexpected value %s specified for ROSCONSOLE_STDOUT_LINE_BUFFERED. Default value 0 " | ||
"will be used. Valid values are 1 or 0.\n", line_buffered.c_str()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please indent this wrapped line with two extra space.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
I tested it locally,
So it works for pipe if the flushing is enabled. |
👍 Nice, thank you for your effort! |
:) |
@@ -394,8 +397,11 @@ void Formatter::print(void* logger_handle, ::ros::console::Level level, const ch | |||
|
|||
fprintf(f, "%s\n", ss.str().c_str()); | |||
|
|||
if (f == stdout) { | |||
fflush(f); | |||
if (g_force_stdout_line_buffered && f == stdout) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we have similar functionality for flushing stderr
? Granted, stdout
should be more common than stderr
, but it seems odd to treat them differently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@IanTheEngineer It seems like stderr has no buffering at all and does not need to be flushed, as far as I know.
Currently the pull request contains multiple commits, I wanted to squash them to one finally, once we decide it's ok. |
…::print When application output is connected to pipe a fully-buffered output is used by default. In this case the logs of application will not be visible for user until there are enough logs to fill the buffer. An example where such behaviour can be observed is launching binary ROS executable from within Android APK. There is not way to connect stdout to TTY and only pipe can be used. As a result the user cannot see the application output. The proposed solution is to force flushing of stdout on every line in ROS logger when a special environment variable is set.
Guys, I squashed the commits to one and reworded the commit message. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While you can squash the commit GitHub also supports to squash them on merge. Whichever you prefer.
if (line_buffered == "1") { | ||
g_force_stdout_line_buffered = true; | ||
} else { | ||
if (line_buffered != "0") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Last nitpick (sorry, didn't see it before): can you use else if
for these two lines instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I will do it tomorrow. Currently I am quite far from my PC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you are ok with it I can just apply the change to your branch (thanks to the latest features in GitHub 😉).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, I changed it to else if
in 52d9671 (plus some style adjustment to match the rest of the file since I was already in the editor 😉). Once CI has passed again this can be merged.
Thanks for your effort on this. It might be good to mention the new environment variable on http://wiki.ros.org/rosconsole with a remark that it is available as of Lunar (for now - it could be considered for backporting in the future). |
@dirk-thomas, I also thought about documentation. Should I somehow edit that wiki? How is it usually done? |
Yes, it would be great if you could edit the wiki page and add the new information. You can annotate the new paragraph using the wiki macro Version to highlight that this is a new feature in ROS Lunar: |
Ok I will do it in a couple of days. Thank you for reviewing. :) |
@dirk-thomas I edited the wiki http://wiki.ros.org/rosconsole#Force_line_buffering_for_ROS_logger , could you please review? |
Thanks! The new paragraph looks great. I only did some little adjustments (see http://wiki.ros.org/action/diff/rosconsole?action=diff&rev1=73&rev2=74). |
…::print (ros#1012) * Force a write of all user-space buffered data for stdout in Formatter::print When application output is connected to pipe a fully-buffered output is used by default. In this case the logs of application will not be visible for user until there are enough logs to fill the buffer. An example where such behaviour can be observed is launching binary ROS executable from within Android APK. There is not way to connect stdout to TTY and only pipe can be used. As a result the user cannot see the application output. The proposed solution is to force flushing of stdout on every line in ROS logger when a special environment variable is set. * minor style change
@dirk-thomas we want this new feature in Kinetic. Can we open the backport PR to kinetic-devel branch? |
Features are in general not backported to already released distros. The reason is a combination of effort and desire for stability / avoiding risk of regression. |
On Android under certain conditions, when user runs binary applications using
ros_comm from within APK, he/she cannot see stdout messages if they are few
lines.
The proposal is to forcibly flush stdout with fflush in this case, as \n in the
end implicitly flushes it on most platforms.
I personally faced this problem on Android when running binaries from within APK. It was impossible to see logs and trace your programs if the logs were a few lines. Though stderr was visible, stdout was not visible. Once I applied this patch it became visible.
If you know how to do it better, please tell me, I'm ready to redo something if it's wrong.