Switch the logging tests from .native() to .string()
[folly.git] / folly / experimental / logging / AsyncFileWriter.cpp
1 /*
2  * Copyright 2004-present Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include <folly/experimental/logging/AsyncFileWriter.h>
17
18 #include <folly/Exception.h>
19 #include <folly/FileUtil.h>
20 #include <folly/experimental/logging/LoggerDB.h>
21
22 using folly::File;
23 using folly::StringPiece;
24
25 namespace folly {
26
27 AsyncFileWriter::AsyncFileWriter(StringPiece path)
28     : AsyncFileWriter{File{path.str(), O_WRONLY | O_APPEND | O_CREAT}} {}
29
30 AsyncFileWriter::AsyncFileWriter(folly::File&& file)
31     : file_{std::move(file)}, ioThread_([this] { ioThread(); }) {}
32
33 AsyncFileWriter::~AsyncFileWriter() {
34   data_->stop = true;
35   messageReady_.notify_one();
36   ioThread_.join();
37 }
38
39 void AsyncFileWriter::writeMessage(StringPiece buffer, uint32_t flags) {
40   return writeMessage(buffer.str(), flags);
41 }
42
43 void AsyncFileWriter::writeMessage(std::string&& buffer, uint32_t flags) {
44   auto data = data_.lock();
45   if ((data->currentBufferSize >= data->maxBufferBytes) &&
46       !(flags & NEVER_DISCARD)) {
47     ++data->numDiscarded;
48     return;
49   }
50
51   data->currentBufferSize += buffer.size();
52   auto* queue = data->getCurrentQueue();
53   queue->emplace_back(std::move(buffer));
54   messageReady_.notify_one();
55 }
56
57 void AsyncFileWriter::flush() {
58   auto data = data_.lock();
59   auto start = data->ioThreadCounter;
60
61   // Wait until ioThreadCounter increments by at least two.
62   // Waiting for a single increment is not sufficient, as this happens after
63   // the I/O thread has swapped the queues, which is before it has actually
64   // done the I/O.
65   while (data->ioThreadCounter < start + 2) {
66     if (data->ioThreadDone) {
67       return;
68     }
69
70     // Enqueue an empty string and wake the I/O thread.
71     // The empty string ensures that the I/O thread will break out of its wait
72     // loop and increment the ioThreadCounter, even if there is no other work
73     // to do.
74     data->getCurrentQueue()->emplace_back();
75     messageReady_.notify_one();
76
77     // Wait for notification from the I/O thread that it has done work.
78     ioCV_.wait(data.getUniqueLock());
79   }
80 }
81
82 void AsyncFileWriter::ioThread() {
83   while (true) {
84     // With the lock held, grab a pointer to the current queue, then increment
85     // the ioThreadCounter index so that other threads will write into the
86     // other queue as we process this one.
87     std::vector<std::string>* ioQueue;
88     size_t numDiscarded;
89     bool stop;
90     {
91       auto data = data_.lock();
92       ioQueue = data->getCurrentQueue();
93       while (ioQueue->empty() && !data->stop) {
94         messageReady_.wait(data.getUniqueLock());
95       }
96
97       ++data->ioThreadCounter;
98       numDiscarded = data->numDiscarded;
99       data->numDiscarded = 0;
100       data->currentBufferSize = 0;
101       stop = data->stop;
102     }
103     ioCV_.notify_all();
104
105     // Write the log messages now that we have released the lock
106     try {
107       performIO(ioQueue);
108     } catch (const std::exception& ex) {
109       onIoError(ex);
110     }
111
112     // clear() empties the vector, but the allocated capacity remains so we can
113     // just reuse it without having to re-allocate in most cases.
114     ioQueue->clear();
115
116     if (numDiscarded > 0) {
117       auto msg = getNumDiscardedMsg(numDiscarded);
118       if (!msg.empty()) {
119         auto ret = folly::writeFull(file_.fd(), msg.data(), msg.size());
120         // We currently ignore errors from writeFull() here.
121         // There's not much we can really do.
122         (void)ret;
123       }
124     }
125
126     if (stop) {
127       data_->ioThreadDone = true;
128       break;
129     }
130   }
131 }
132
133 void AsyncFileWriter::performIO(std::vector<std::string>* ioQueue) {
134   // kNumIovecs controls the maximum number of strings we write at once in a
135   // single writev() call.
136   constexpr int kNumIovecs = 64;
137   std::array<iovec, kNumIovecs> iovecs;
138
139   size_t idx = 0;
140   while (idx < ioQueue->size()) {
141     int numIovecs = 0;
142     while (numIovecs < kNumIovecs && idx < ioQueue->size()) {
143       const auto& str = (*ioQueue)[idx];
144       iovecs[numIovecs].iov_base = const_cast<char*>(str.data());
145       iovecs[numIovecs].iov_len = str.size();
146       ++numIovecs;
147       ++idx;
148     }
149
150     auto ret = folly::writevFull(file_.fd(), iovecs.data(), numIovecs);
151     folly::checkUnixError(ret, "writeFull() failed");
152   }
153 }
154
155 void AsyncFileWriter::onIoError(const std::exception& ex) {
156   LoggerDB::internalWarning(
157       __FILE__,
158       __LINE__,
159       "error writing to log file ",
160       file_.fd(),
161       " in AsyncFileWriter: ",
162       folly::exceptionStr(ex));
163 }
164
165 std::string AsyncFileWriter::getNumDiscardedMsg(size_t numDiscarded) {
166   // We may want to make this customizable in the future (e.g., to allow it to
167   // conform to the LogFormatter style being used).
168   // For now just return a simple fixed message.
169   return folly::to<std::string>(
170       numDiscarded,
171       " log messages discarded: logging faster than we can write\n");
172 }
173 }