Boost C++ Libraries: Ticket #11016: Boost file logging misbehaves when file system is full https://svn.boost.org/trac10/ticket/11016 <p> I'm using boost log with log files. Code is simple and works fine: </p> <pre class="wiki">FileSinkPtr s = boost::make_shared&lt;FileSinkT&gt;( keywords::file_name = path + "-%N.log", keywords::rotation_size = rotation_size); string parent = boost::filesystem::path(path).parent_path().native(); s-&gt;locked_backend()-&gt;set_file_collector(sinks::file::make_collector(keywords::target = parent, keywords::max_size = dir_size)); s-&gt;locked_backend()-&gt;scan_for_files(); </pre><p> Rotation size is set to max 512 kB, and log dir size to max 10 MB. </p> <p> All this works fine. When I run a test that does some logging, I see the log files created and rotated as they should be. </p> <p> In an unrelated test of our code, I decided to test the robustness of file-related operations, to see whether our code would hold up when the file system was full. So, I created a small ramdisk that I can fill up to see what happens. </p> <p> It turns out that, once the file system is full, boost log does bad things. Here are the contents of the log dir after a single run of my test: </p> <pre class="wiki">$ ls -l total 680 -rw-rw-r-- 1 michi michi 524172 Feb 12 11:38 PusherScope-0.log -rw-rw-r-- 1 michi michi 170249 Feb 12 11:38 PusherScope-1.log </pre><p> This is in a 10 MB file system: </p> <pre class="wiki">$ df Filesystem 1K-blocks Used Available Use% Mounted on tmpfs 10240 3480 6760 34% /home/michi/tmp/ramdisk </pre><p> The test runs several processes, some of which also log into the same file system (but each process logs into a different directory). </p> <p> Running the test a second time, I see: </p> <pre class="wiki"> $ ls -l total 1360 -rw-rw-r-- 1 michi michi 524172 Feb 12 11:38 PusherScope-0.log -rw-rw-r-- 1 michi michi 170249 Feb 12 11:38 PusherScope-1.log -rw-rw-r-- 1 michi michi 524252 Feb 12 11:40 PusherScope-2.log -rw-rw-r-- 1 michi michi 170169 Feb 12 11:40 PusherScope-3.log </pre><p> Things are still fine, just as I would expect. </p> <pre class="wiki">$ df Filesystem 1K-blocks Used Available Use% Mounted on tmpfs 10240 6960 3280 68% /home/michi/tmp/ramdisk </pre><p> Running the test a third time causes the file system to fill up: </p> <pre class="wiki">$ df Filesystem 1K-blocks Used Available Use% Mounted on tmpfs 10240 10240 0 100% /home/michi/tmp/ramdisk </pre><p> Now, when I look at the log directory, I find five non-empty log files in there: </p> <pre class="wiki">$ ls -l -rw-rw-r-- 1 michi michi 170249 Feb 12 11:38 PusherScope-1.log -rw-rw-r-- 1 michi michi 524252 Feb 12 11:40 PusherScope-2.log -rw-rw-r-- 1 michi michi 170169 Feb 12 11:40 PusherScope-3.log -rw-rw-r-- 1 michi michi 524261 Feb 12 11:42 PusherScope-4.log -rw-rw-r-- 1 michi michi 135168 Feb 12 11:42 PusherScope-5.log ... </pre><p> In addition, there are now 280 (!) empty log files there. Small excerpt: </p> <pre class="wiki">total 2004 -rw-rw-r-- 1 michi michi 524172 Feb 12 11:38 PusherScope-0.log -rw-rw-r-- 1 michi michi 0 Feb 12 11:42 PusherScope-100.log -rw-rw-r-- 1 michi michi 0 Feb 12 11:42 PusherScope-101.log -rw-rw-r-- 1 michi michi 0 Feb 12 11:42 PusherScope-102.log -rw-rw-r-- 1 michi michi 0 Feb 12 11:42 PusherScope-103.log -rw-rw-r-- 1 michi michi 0 Feb 12 11:42 PusherScope-104.log etc, etc... </pre><p> It's the same story for other log directories in the file system, where other processes also log and do the exact same thing. </p> <p> If I continue to run the test in a loop, this continues, adding more and more empty log files. Eventually, the file system runs out of inodes: </p> <pre class="wiki">$ df -i Filesystem Inodes IUsed IFree IUse% Mounted on tmpfs 2040639 1213366 827273 60% /home/michi/tmp/ramdisk </pre><p> (I stopped the test at that point, by which I had more than a million empty files.) </p> en-us Boost C++ Libraries /htdocs/site/boost.png https://svn.boost.org/trac10/ticket/11016 Trac 1.4.3 michi.henning@… Thu, 12 Feb 2015 02:00:49 GMT version changed https://svn.boost.org/trac10/ticket/11016#comment:1 https://svn.boost.org/trac10/ticket/11016#comment:1 <ul> <li><strong>version</strong> <span class="trac-field-old">Boost 1.57.0</span> → <span class="trac-field-new">Boost 1.55.0</span> </li> </ul> Ticket Michi Henning <michi.henning@…> Thu, 12 Feb 2015 09:13:03 GMT <link>https://svn.boost.org/trac10/ticket/11016#comment:2 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:2</guid> <description> <p> Without having looked at the code, my guess is that the code creates a new log file, tries to write to it, gets ENOSPC, and doesn't unlink the log file at that point. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Andrey Semashev</dc:creator> <pubDate>Thu, 12 Feb 2015 10:58:21 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/11016#comment:3 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:3</guid> <description> <p> What I think is happening is the file stream which is used to write the log file becomes !good() when there is no space to write a record. No exception is thrown but on the next record the library sees that the stream is not operational and rotates the file. This is where lots of empty files appear. </p> <p> I agree that the current behavior is not correct, but it is not clear what it should be. I can throw exceptions when write fails (although I won't be able to detect the cause of the failure) but as a recovery procedure I still have to rotate the file on the next record so that the library repairs itself when environment becomes normal again. I can add a check and not actually perform rotation when the file is empty, although this makes behavior less obvious. </p> </description> <category>Ticket</category> </item> <item> <author>Michi Henning <michi.henning@…></author> <pubDate>Thu, 12 Feb 2015 11:15:17 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/11016#comment:4 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:4</guid> <description> <p> Eating up all available inodes (on top of the file system being full already) is not right. If any attempt to write a file returns ENOSPC, all write activity should be abandoned. In addition, I would unlink whichever file incurs that error. The file system is already full, and things are pretty much hopeless at that point anyway until someone comes along and makes more space available. So, losing whichever log file couldn't be written/appended to is not a big deal. </p> <p> I don't think throwing an exception would help, in the sense that no code that uses boost::log expects to get an exception from one of the API calls. Throwing from a logging library just doesn't make sense. What am I supposed to do? Catch the exception and log an error about it having happened? ;-) </p> <p> I suspect some sort of stateful error handling is necessary here. If any write returns ENOSPC, take note of that fact and remember it. Disable log rotation for a while. Occasionally, maybe every 10-60 seconds or so, check if there is space available again. If not, rinse and repeat. There is no point in continuously bashing away at a file system that is full already… </p> <p> Another check might be to test whether a file is newly-created and empty. If so, the first write to the file should immediately unlink it again. That way, at least there won't be the endless attempts to create more and more files that can't be written to anyway. </p> </description> <category>Ticket</category> </item> <item> <author>Michi Henning <michi.henning@…></author> <pubDate>Thu, 12 Feb 2015 11:18:41 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/11016#comment:5 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:5</guid> <description> <blockquote class="citation"> <blockquote> <p> If so, the first write to the file should immediately unlink it again. </p> </blockquote> </blockquote> <p> My apologies, I meant to write "If so, the first write, if it fails, should immediately unlink it again." </p> <p> The idea is that only files that exist already and have at least one byte of content are written to unconditionally. But, files that were just created and can't be written to should be unlinked if a write fails because the file system is full. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Andrey Semashev</dc:creator> <pubDate>Thu, 12 Feb 2015 12:43:22 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/11016#comment:6 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:6</guid> <description> <p> Replying to <a class="ticket" href="https://svn.boost.org/trac10/ticket/11016#comment:4" title="Comment 4">Michi Henning &lt;michi.henning@…&gt;</a>: </p> <blockquote class="citation"> <p> Eating up all available inodes (on top of the file system being full already) is not right. </p> </blockquote> <p> Not arguing with that. </p> <blockquote class="citation"> <p> If any attempt to write a file returns ENOSPC, all write activity should be abandoned. In addition, I would unlink whichever file incurs that error. </p> </blockquote> <p> Not if the file is not empty. You wouldn't like losing written logs, would you? </p> <blockquote class="citation"> <p> I don't think throwing an exception would help, in the sense that no code that uses boost::log expects to get an exception from one of the API calls. Throwing from a logging library just doesn't make sense. What am I supposed to do? Catch the exception and log an error about it having happened? ;-) </p> </blockquote> <p> I could suggest multiple sensible reactions to such failure, like displaying a notification in GUI or cleaning up archived logs or terminating the app. The point is that the library has to indicate the problem, and it is the application's prerogative to decide how to react. You can suppress all exceptions from the library, if you like. </p> <blockquote class="citation"> <p> I suspect some sort of stateful error handling is necessary here. If any write returns ENOSPC, take note of that fact and remember it. Disable log rotation for a while. Occasionally, maybe every 10-60 seconds or so, check if there is space available again. If not, rinse and repeat. There is no point in continuously bashing away at a file system that is full already… </p> <p> Another check might be to test whether a file is newly-created and empty. If so, the first write to the file should immediately unlink it again. That way, at least there won't be the endless attempts to create more and more files that can't be written to anyway. </p> </blockquote> <p> Note that the library is not timer-driven but rather event-driven (where events are represented with log records as they are emitted by the app). For this reason time-based solutions have limited sense. </p> </description> <category>Ticket</category> </item> <item> <author>Michi Henning <michi.henning@…></author> <pubDate>Thu, 12 Feb 2015 22:45:39 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/11016#comment:7 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:7</guid> <description> <p> Replying to <a class="ticket" href="https://svn.boost.org/trac10/ticket/11016#comment:6" title="Comment 6">andysem</a>: </p> <blockquote class="citation"> <blockquote class="citation"> <p> If any attempt to write a file returns ENOSPC, all write activity should be abandoned. In addition, I would unlink whichever file incurs that error. </p> </blockquote> <p> Not if the file is not empty. You wouldn't like losing written logs, would you? </p> </blockquote> <p> Well… It's questionable whether the log file is valuable in this case. It's a bit like running out of memory: the system is in a state where correct functioning of some components is impossible. Seeing that this case will, in practice, be rare, unlinking the file would be quite acceptable, I believe. Running out of space really is an extreme case, and it justifies extreme recovery action, IMO. </p> <blockquote class="citation"> <blockquote class="citation"> <p> I don't think throwing an exception would help, in the sense that no code that uses boost::log expects to get an exception from one of the API calls. Throwing from a logging library just doesn't make sense. What am I supposed to do? Catch the exception and log an error about it having happened? ;-) </p> </blockquote> <p> I could suggest multiple sensible reactions to such failure, like displaying a notification in GUI or cleaning up archived logs or terminating the app. The point is that the library has to indicate the problem, and it is the application's prerogative to decide how to react. You can suppress all exceptions from the library, if you like. </p> </blockquote> <p> I don't think a global suppression would help, at least not in all cases. I'm logging from a library. I don't know what the code that links with my library does. So, I've been very careful not to mess with the global boost log state and use separate loggers and sinks that don't interfere with the application, even if it happens to use boost log. I can't just change a process-wide setting from within a library. That's just as forbidden as changing umask or working directory would be. </p> <p> In my opinion, throwing from log methods (at least the ones that create log messages) is a big no-no. If a log message can't be written, there is typically nothing that the code that does the logging can do. All it would achieve is that I would have to clutter the calling code with masses of catch handlers. Throwing from the log rotate call might be OK, if it's well documented that the method can throw. That's because any setup/initialization calls for logging are typically in a section of code that might have at least a chance of meaningfully reporting an error. But, seeing that the log rotation happens transparently while I'm logging, I don't think that would help much. </p> <p> Throwing from the BOOST_LOG macros is definitely out, as far as I can see. All that would achieve is send most programs to terminate(), quick smart. </p> <blockquote class="citation"> <p> Note that the library is not timer-driven but rather event-driven (where events are represented with log records as they are emitted by the app). For this reason time-based solutions have limited sense. </p> </blockquote> <p> Timers wouldn't be needed. Just remember the current time if a failure occurs. Then, when trying to write, if in the error state, check the current time and, if within the ban period, skip the write. Once a write works, re-enter the no-error state, to avoid the overhead of checking the current time. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Andrey Semashev</dc:creator> <pubDate>Fri, 13 Feb 2015 07:48:48 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/11016#comment:8 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:8</guid> <description> <p> Replying to <a class="ticket" href="https://svn.boost.org/trac10/ticket/11016#comment:7" title="Comment 7">Michi Henning &lt;michi.henning@…&gt;</a>: </p> <blockquote class="citation"> <p> I don't think a global suppression would help, at least not in all cases. I'm logging from a library. </p> </blockquote> <p> I'm not saying global suppression. You can suppress exceptions on different levels, including the logger you use in your library. </p> <blockquote class="citation"> <p> In my opinion, throwing from log methods (at least the ones that create log messages) is a big no-no. If a log message can't be written, there is typically nothing that the code that does the logging can do. All it would achieve is that I would have to clutter the calling code with masses of catch handlers. </p> </blockquote> <p> Have a look at exception handlers, you don't have to write try/catch everywhere. </p> <blockquote class="citation"> <p> Timers wouldn't be needed. Just remember the current time if a failure occurs. Then, when trying to write, if in the error state, check the current time and, if within the ban period, skip the write. Once a write works, re-enter the no-error state, to avoid the overhead of checking the current time. </p> </blockquote> <p> What does such record peeling give? Other than losing some records which could have been written when free space appears, it doesn't seem to do any significant effect. </p> </description> <category>Ticket</category> </item> <item> <author>Michi Henning <michi.henning@…></author> <pubDate>Fri, 13 Feb 2015 09:33:42 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/11016#comment:9 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:9</guid> <description> <p> Replying to <a class="ticket" href="https://svn.boost.org/trac10/ticket/11016#comment:8" title="Comment 8">andysem</a>: </p> <blockquote class="citation"> <p> I'm not saying global suppression. You can suppress exceptions on different levels, including the logger you use in your library. </p> </blockquote> <p> Ah, OK, I didn't know that, thanks! Where do I look for this? Any doc on that? </p> <blockquote class="citation"> <blockquote class="citation"> <p> In my opinion, throwing from log methods (at least the ones that create log messages) is a big no-no. If a log message can't be written, there is typically nothing that the code that does the logging can do. All it would achieve is that I would have to clutter the calling code with masses of catch handlers. </p> </blockquote> <p> Have a look at exception handlers, you don't have to write try/catch everywhere. </p> </blockquote> <p> OK, again thanks, I didn't know that! </p> <blockquote class="citation"> <blockquote class="citation"> <p> Timers wouldn't be needed. Just remember the current time if a failure occurs. Then, when trying to write, if in the error state, check the current time and, if within the ban period, skip the write. Once a write works, re-enter the no-error state, to avoid the overhead of checking the current time. </p> </blockquote> <p> What does such record peeling give? Other than losing some records which could have been written when free space appears, it doesn't seem to do any significant effect. </p> </blockquote> <p> The savings would be minor. Basically, you'd avoid trying to write for a while, dealing with the failed calls and potentially re-establishing the previously bad situation. But I agree, this is just an embellishment and won't solve the fundamental problem. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Andrey Semashev</dc:creator> <pubDate>Fri, 13 Feb 2015 10:28:18 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/11016#comment:10 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:10</guid> <description> <p> Replying to <a class="ticket" href="https://svn.boost.org/trac10/ticket/11016#comment:9" title="Comment 9">Michi Henning &lt;michi.henning@…&gt;</a>: </p> <blockquote class="citation"> <p> Replying to <a class="ticket" href="https://svn.boost.org/trac10/ticket/11016#comment:8" title="Comment 8">andysem</a>: </p> <blockquote class="citation"> <p> I'm not saying global suppression. You can suppress exceptions on different levels, including the logger you use in your library. </p> </blockquote> <p> Ah, OK, I didn't know that, thanks! Where do I look for this? Any doc on that? </p> </blockquote> <p> See here: <a href="http://www.boost.org/doc/libs/release/libs/log/doc/html/log/detailed/sources.html#log.detailed.sources.exception_handling">http://www.boost.org/doc/libs/release/libs/log/doc/html/log/detailed/sources.html#log.detailed.sources.exception_handling</a> </p> <blockquote class="citation"> <blockquote class="citation"> <p> Have a look at exception handlers, you don't have to write try/catch everywhere. </p> </blockquote> <p> OK, again thanks, I didn't know that! </p> </blockquote> <p> <a href="http://www.boost.org/doc/libs/release/libs/log/doc/html/log/detailed/utilities.html#log.detailed.utilities.exception_handlers">http://www.boost.org/doc/libs/release/libs/log/doc/html/log/detailed/utilities.html#log.detailed.utilities.exception_handlers</a> </p> </description> <category>Ticket</category> </item> <item> <author>Michi Henning <michi.henning@…></author> <pubDate>Fri, 13 Feb 2015 10:48:00 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/11016#comment:11 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:11</guid> <description> <p> Thank you! I missed this when I read the documentation. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Andrey Semashev</dc:creator> <pubDate>Sat, 14 Feb 2015 16:22:45 GMT</pubDate> <title>status changed; resolution set https://svn.boost.org/trac10/ticket/11016#comment:12 https://svn.boost.org/trac10/ticket/11016#comment:12 <ul> <li><strong>status</strong> <span class="trac-field-old">new</span> → <span class="trac-field-new">closed</span> </li> <li><strong>resolution</strong> → <span class="trac-field-new">fixed</span> </li> </ul> <p> Fixed in <a class="ext-link" href="https://github.com/boostorg/log/commit/7ebfd3b6c4772cfa09c54366e96e4b5e8c079af6"><span class="icon">​</span>https://github.com/boostorg/log/commit/7ebfd3b6c4772cfa09c54366e96e4b5e8c079af6</a>. </p> <p> I didn't add exceptions yet but created a separate ticket <a class="new ticket" href="https://svn.boost.org/trac10/ticket/11026" title="#11026: Feature Requests: Add exceptions to file sinks when writing fails (new)">#11026</a> for that. This will probably be a more complex task than I originally thought. </p> Ticket Michi Henning <michi.henning@…> Mon, 16 Feb 2015 23:24:39 GMT status changed; resolution deleted https://svn.boost.org/trac10/ticket/11016#comment:13 https://svn.boost.org/trac10/ticket/11016#comment:13 <ul> <li><strong>status</strong> <span class="trac-field-old">closed</span> → <span class="trac-field-new">reopened</span> </li> <li><strong>resolution</strong> <span class="trac-field-deleted">fixed</span> </li> </ul> <p> I just built 1.57 from source with your patch applied and re-ran my test. I'm still seeing lots of empty files being left behind in the log directories. There are not as many: it's about a dozen new empty files per run now (instead of hundreds). But the problem isn't completely fixed yet, it seems. </p> Ticket Andrey Semashev Tue, 17 Feb 2015 03:47:28 GMT <link>https://svn.boost.org/trac10/ticket/11016#comment:14 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:14</guid> <description> <p> I can't reproduce this, I'm only having one file per run, which is expected. I'm testing with the rotating_file example from the library, modified to work similarly to your setup. Can you try this example and Boost from git develop branch? </p> </description> <category>Ticket</category> </item> <item> <author>Michi Henning <michi.henning@…></author> <pubDate>Tue, 17 Feb 2015 03:56:19 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/11016#comment:15 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:15</guid> <description> <p> I'll try and put together a stand-alone test case. Could the few log files I still see being left empty be related to the fact that I have several process running concurrently, each of which uses a different log directory? </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Andrey Semashev</dc:creator> <pubDate>Tue, 17 Feb 2015 04:02:58 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/11016#comment:16 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:16</guid> <description> <p> Yes, that's possible. Also, different processes must not use the same directories because the library does no interprocess synchronization. File names may clash and limits are not maintained in this case. </p> </description> <category>Ticket</category> </item> <item> <author>Michi Henning <michi.henning@…></author> <pubDate>Tue, 17 Feb 2015 05:19:33 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/11016#comment:17 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:17</guid> <description> <p> There is definitely no directory sharing among these processes. It's just that there are a bunch of them running, each logging into a separate log dir. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Andrey Semashev</dc:creator> <pubDate>Tue, 17 Feb 2015 06:40:36 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/11016#comment:18 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:18</guid> <description> <p> Does each process have a separate directory set as the target directory for the file collector? If so, I don't see how multiple empty files can appear in each of these directories after a single run. That is unless you recreate the sink or perform file rotation manually. </p> <p> Please, try to run the test with a single process to simplify things. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Andrey Semashev</dc:creator> <pubDate>Sun, 22 Feb 2015 19:22:03 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/11016#comment:19 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:19</guid> <description> <p> Any luck reproducing the issue with the library example? </p> </description> <category>Ticket</category> </item> <item> <author>Michi Henning <michi.henning@…></author> <pubDate>Sun, 22 Feb 2015 21:36:04 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/11016#comment:20 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:20</guid> <description> <p> My apologies Andy, I haven't had time yet to get back to this. Will look at it today. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>anonymous</dc:creator> <pubDate>Mon, 23 Feb 2015 04:44:10 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/11016#comment:21 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:21</guid> <description> <p> Below is a stand-alone test case that shows the problem. This is with boost 1.57 with your patch applied. </p> <p> I create a 5 MB ram disk: </p> <pre class="wiki">sudo mount -o size=5M -t tmpfs none /home/michi/tmp/ramdisk </pre><p> Note that the code below hard-wires this path. Please adjust as necessary. </p> <p> When I run the code in a loop, I see the empty log files appearing once the file system fills up (one empty file per run). </p> <pre class="wiki">while :; do ./a.out ; done </pre><pre class="wiki">int main() { namespace keywords = boost::log::keywords; namespace logging = boost::log; namespace sinks = boost::log::sinks; logging::sources::severity_channel_logger_mt&lt;&gt; logger; typedef logging::sinks::asynchronous_sink&lt;logging::sinks::text_file_backend&gt; FileSinkT; typedef boost::shared_ptr&lt;FileSinkT&gt; FileSinkPtr; FileSinkPtr file_sink = boost::make_shared&lt;FileSinkT&gt;( keywords::file_name = "/home/michi/tmp/ramdisk/log-%N.log", keywords::rotation_size = 1024 * 512); file_sink-&gt;locked_backend()-&gt;set_file_collector(sinks::file::make_collector( keywords::target = "/home/michi/tmp/ramdisk", keywords::max_size = 1024 * 1024 * 10)); file_sink-&gt;locked_backend()-&gt;scan_for_files(); file_sink-&gt;locked_backend()-&gt;auto_flush(true); logging::core::get()-&gt;add_sink(file_sink); for (int i = 0; i &lt; 100000; ++i) { BOOST_LOG(logger) &lt;&lt; "Hello"; } std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } </pre> </description> <category>Ticket</category> </item> <item> <dc:creator>Andrey Semashev</dc:creator> <pubDate>Mon, 23 Feb 2015 10:11:14 GMT</pubDate> <title>status changed; resolution set https://svn.boost.org/trac10/ticket/11016#comment:22 https://svn.boost.org/trac10/ticket/11016#comment:22 <ul> <li><strong>status</strong> <span class="trac-field-old">reopened</span> → <span class="trac-field-new">closed</span> </li> <li><strong>resolution</strong> → <span class="trac-field-new">fixed</span> </li> </ul> <blockquote class="citation"> <p> When I run the code in a loop, I see the empty log files appearing once the file system fills up (one empty file per run). </p> </blockquote> <p> Yes, that is how it should work. The empty file appears because the file is rotated at application exit, and the new run starts a new file. You can add std::ios::app to the file open flags in the sink backend if you want to append to the previous file on every next run. </p> Ticket Michi Henning <michi.henning@…> Mon, 23 Feb 2015 10:16:42 GMT <link>https://svn.boost.org/trac10/ticket/11016#comment:23 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:23</guid> <description> <p> Appending will be useful, thanks. I didn't know about this option. When I searched for appending to earlier log files recently, I found some threads in discussion forums that said that this is impossible. I take it that this is fairly recent? </p> <p> As to the empty file, the problem is that there a new empty file created on every run. So, while the file system is full, I get a new empty file for each and every run of my process. (Many of my processes are short-lived.) That doesn't seem right to me. Why leave an empty log file behind? </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Andrey Semashev</dc:creator> <pubDate>Mon, 23 Feb 2015 10:25:21 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/11016#comment:24 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:24</guid> <description> <blockquote class="citation"> <p> Appending will be useful, thanks. I didn't know about this option. When I searched for appending to earlier log files recently, I found some threads in discussion forums that said that this is impossible. I take it that this is fairly recent? </p> </blockquote> <p> No, it's there for quite some time. </p> <blockquote class="citation"> <p> As to the empty file, the problem is that there a new empty file created on every run. So, while the file system is full, I get a new empty file for each and every run of my process. (Many of my processes are short-lived.) That doesn't seem right to me. Why leave an empty log file behind? </p> </blockquote> <p> Unless you enable appending, you get a new file regardless of the file emptiness or space exhaustion. This behavior seems logical to me. Empty files can also be an indication of a certain behavior of the app, so they are not completely worthless. </p> </description> <category>Ticket</category> </item> <item> <author>Michi Henning <michi.henning@…></author> <pubDate>Mon, 23 Feb 2015 21:26:54 GMT</pubDate> <title>status changed; resolution deleted https://svn.boost.org/trac10/ticket/11016#comment:25 https://svn.boost.org/trac10/ticket/11016#comment:25 <ul> <li><strong>status</strong> <span class="trac-field-old">closed</span> → <span class="trac-field-new">reopened</span> </li> <li><strong>resolution</strong> <span class="trac-field-deleted">fixed</span> </li> </ul> <p> Replying to <a class="ticket" href="https://svn.boost.org/trac10/ticket/11016#comment:22" title="Comment 22">andysem</a>: </p> <blockquote class="citation"> <p> Yes, that is how it should work. The empty file appears because the file is rotated at application exit, and the new run starts a new file. You can add std::ios::app to the file open flags in the sink backend if you want to append to the previous file on every next run. </p> </blockquote> <p> I just tried this, by adding keywords::open_mode = std::ios::app when I create the sink. But it doesn't appear to change anything. The last log file from the previous run isn't appended to; instead, a new log file is created regardless. Other people seem to have had the same experience: </p> <p> <a class="ext-link" href="http://stackoverflow.com/questions/8418917/boost-log-how-to-configure-a-text-sink-backend-to-append-to-rotated-files"><span class="icon">​</span>http://stackoverflow.com/questions/8418917/boost-log-how-to-configure-a-text-sink-backend-to-append-to-rotated-files</a> </p> <p> Am I doing something wrong? </p> <blockquote class="citation"> <p> Unless you enable appending, you get a new file regardless of the file emptiness or space exhaustion. This behavior seems logical to me. Empty files can also be an indication of a certain behavior of the app, so they are not completely worthless. </p> </blockquote> <p> I honestly can't think of a reason why an empty file would be useful. </p> <p> I experimented with this some more. So, I run the test case until the file system is full, at which point it leaves an empty log file behind. Now I run the test three more times, so I end up with four empty log files (plus a whole bunch of non-empty ones). Then I delete some non-empty log files so there is plenty of room again, and run the test one more time. As expected, it now creates two new non-empty log files. But the file rotation code never removes the empty log files, even after there is space in the file system. </p> <p> I'm sorry, but this is still not right. Once the file system fills up, boost log creates an empty log file on every file rotation. These empty files accumulate indefinitely and are never removed. That's a permanent resource leak. </p> <p> The code should check on the first write after creating a log file whether the write succeeded. Otherwise, something is seriously wrong, and it should unlink the file it just created. </p> Ticket Michi Henning <michi.henning@…> Mon, 23 Feb 2015 22:53:36 GMT <link>https://svn.boost.org/trac10/ticket/11016#comment:26 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:26</guid> <description> <p> Just to be clear: the sole purpose of creating a file is to write to it. If the file can't be written to, there is no point in having the file. So, if a write fails for any reason AND the file is empty at that point, unlink the file. If the file has some contents at that point already, by all means, keep it. As far as I can see, that would get rid of the empty files. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Andrey Semashev</dc:creator> <pubDate>Tue, 24 Feb 2015 07:52:44 GMT</pubDate> <title>status changed; resolution set https://svn.boost.org/trac10/ticket/11016#comment:27 https://svn.boost.org/trac10/ticket/11016#comment:27 <ul> <li><strong>status</strong> <span class="trac-field-old">reopened</span> → <span class="trac-field-new">closed</span> </li> <li><strong>resolution</strong> → <span class="trac-field-new">fixed</span> </li> </ul> <blockquote class="citation"> <p> I just tried this, by adding keywords::open_mode = std::ios::app when I create the sink. But it doesn't appear to change anything. </p> </blockquote> <p> The newly generated file name must match the old file name, then it will append to the old file. This means that the file name must be sufficiently stable over time and not contain the counter. If you still can't get it to work, please, create a separate ticket. </p> <blockquote class="citation"> <p> I'm sorry, but this is still not right. Once the file system fills up, boost log creates an empty log file on every file rotation. These empty files accumulate indefinitely and are never removed. That's a permanent resource leak. </p> </blockquote> <p> This is not a leak. Empty files, as well as non-empty ones are accounted for and deleted when threshold is reached. See the docs <a href="http://www.boost.org/doc/libs/release/libs/log/doc/html/log/detailed/sink_backends.html#log.detailed.sink_backends.text_file.managing_rotated_files">http://www.boost.org/doc/libs/release/libs/log/doc/html/log/detailed/sink_backends.html#log.detailed.sink_backends.text_file.managing_rotated_files</a>, especially take note about the min_free_space parameter. </p> <p> I don't see the reason to change the behavior wrt empty files. They are not special and will be processed just like any other log file. </p> Ticket Michi Henning <michi.henning@…> Tue, 24 Feb 2015 08:32:47 GMT <link>https://svn.boost.org/trac10/ticket/11016#comment:28 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:28</guid> <description> <p> Replying to <a class="ticket" href="https://svn.boost.org/trac10/ticket/11016#comment:27" title="Comment 27">andysem</a>: </p> <blockquote class="citation"> <p> This is not a leak. Empty files, as well as non-empty ones are accounted for and deleted when threshold is reached. See the docs <a href="http://www.boost.org/doc/libs/release/libs/log/doc/html/log/detailed/sink_backends.html#log.detailed.sink_backends.text_file.managing_rotated_files">http://www.boost.org/doc/libs/release/libs/log/doc/html/log/detailed/sink_backends.html#log.detailed.sink_backends.text_file.managing_rotated_files</a>, especially take note about the min_free_space parameter. </p> <p> I don't see the reason to change the behavior wrt empty files. They are not special and will be processed just like any other log file. </p> </blockquote> <p> Andy, the point is that the threshold isn't reached while the file system is full. Therefore, while the "file system full" condition persists, a potentially unbounded number of inodes is used up, making a bad situation worse. If the file system free space falls below the threshold then, yes, eventually the empty inodes are reclaimed. But that doesn't happen until the threshold *is* reached enough times for all the logs to rotate through until we get to the empty ones. In effect, this means that the empty files can keep kicking around for potentially weeks or months. </p> <p> I'll have a look at min_free_space, thanks for the tip! But, as far as I can see, it would have to be at least as large max_size to have any effect? </p> <p> Is it really that hard to check whether a write failed and, if so, stat the file and unlink it if empty? It seems like a simple fix, and it would get rid of the empty files. We'd have a more robust system that way. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Andrey Semashev</dc:creator> <pubDate>Tue, 24 Feb 2015 09:05:59 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/11016#comment:29 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:29</guid> <description> <p> Replying to <a class="ticket" href="https://svn.boost.org/trac10/ticket/11016#comment:28" title="Comment 28">Michi Henning &lt;michi.henning@…&gt;</a>: </p> <blockquote class="citation"> <p> Andy, the point is that the threshold isn't reached while the file system is full. Therefore, while the "file system full" condition persists, a potentially unbounded number of inodes is used up, making a bad situation worse. </p> </blockquote> <p> If you only set the max_size limit then yes, the files will keep piling up. The proper fix for that is to set min_free_space - I assume you don't want the empty files to appear in the first place, do you? </p> <blockquote class="citation"> <p> I'll have a look at min_free_space, thanks for the tip! But, as far as I can see, it would have to be at least as large max_size to have any effect? </p> </blockquote> <p> No, these limits are not related. </p> <blockquote class="citation"> <p> Is it really that hard to check whether a write failed and, if so, stat the file and unlink it if empty? It seems like a simple fix, and it would get rid of the empty files. We'd have a more robust system that way. </p> </blockquote> <p> The question is not about hard. It's about consistency. A special behavior should be backed by a good reason. </p> </description> <category>Ticket</category> </item> <item> <author>Michi Henning <michi.henning@…></author> <pubDate>Tue, 24 Feb 2015 10:12:59 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/11016#comment:30 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:30</guid> <description> <p> Replying to <a class="ticket" href="https://svn.boost.org/trac10/ticket/11016#comment:29" title="Comment 29">andysem</a>: </p> <blockquote class="citation"> <p> If you only set the max_size limit then yes, the files will keep piling up. The proper fix for that is to set min_free_space - I assume you don't want the empty files to appear in the first place, do you? </p> </blockquote> <p> Exactly :-) </p> <blockquote class="citation"> <p> No, these limits are not related. </p> </blockquote> <p> OK, I'll tinker with that tomorrow, thanks! </p> <blockquote class="citation"> <blockquote class="citation"> <p> Is it really that hard to check whether a write failed and, if so, stat the file and unlink it if empty? It seems like a simple fix, and it would get rid of the empty files. We'd have a more robust system that way. </p> </blockquote> <p> The question is not about hard. It's about consistency. A special behavior should be backed by a good reason. </p> </blockquote> <p> I strongly agree with that. I'm coming at this from the perspective of a first-time user of boost log. So there is this file rotation thing, I can specify file name patterns, limit individual log file sizes, directory size, and so. All good. Works really well, no problem. Then I do an (admittedly extreme) test, trying it with a full file system. </p> <p> Now that you've told me about min_free_space, I go "OK, so if I set that, it'll to the right thing". But that come as a real surprise to me. It effectively means that, if the file system is full, and I haven't set min_free_space, I end up with lots of empty log files. In other words, how am I supposed to know that not setting min_free_space causes empty log files? </p> <p> Prior to your patch, the code was leaking lots of inodes. Now it leaks many fewer. Thanks again for that fix! But, is there really a difference between leaking many inodes as opposed to one? I can't think of what the utility of an empty log file would be, seeing that it contains no information, and was created only in order to add information to the file in the first place. What's wrong with unlinking a file that cannot be written to if the file is empty? Alternatively, what's to be gained by leaving the file behind? </p> </description> <category>Ticket</category> </item> <item> <author>Michi Henning <michi.henning@…></author> <pubDate>Wed, 25 Feb 2015 01:13:01 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/11016#comment:31 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11016#comment:31</guid> <description> <p> The min_free_space setting avoids the problem, thanks for that tip! </p> <p> I still believe I shouldn't have to set this though. It's certainly non-obvious that leaving it unset can cause empty log files to accumulate, in particular since it is highly unlikely that people would ever notice during testing. </p> </description> <category>Ticket</category> </item> </channel> </rss>