OLD | NEW |
1 // Copyright (c) 2009 The Chromium OS Authors. All rights reserved. | 1 // Copyright (c) 2009 The Chromium OS Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "update_engine/libcurl_http_fetcher.h" | 5 #include "update_engine/libcurl_http_fetcher.h" |
6 #include <algorithm> | 6 #include <algorithm> |
7 #include "chromeos/obsolete_logging.h" | 7 #include "chromeos/obsolete_logging.h" |
8 | 8 |
9 using std::max; | 9 using std::max; |
10 using std::make_pair; | 10 using std::make_pair; |
11 | 11 |
12 // This is a concrete implementation of HttpFetcher that uses libcurl to do the | 12 // This is a concrete implementation of HttpFetcher that uses libcurl to do the |
13 // http work. | 13 // http work. |
14 | 14 |
15 namespace chromeos_update_engine { | 15 namespace chromeos_update_engine { |
16 | 16 |
17 LibcurlHttpFetcher::~LibcurlHttpFetcher() { | 17 LibcurlHttpFetcher::~LibcurlHttpFetcher() { |
18 CleanUp(); | 18 CleanUp(); |
19 } | 19 } |
20 | 20 |
21 void LibcurlHttpFetcher::ResumeTransfer(const std::string& url) { | 21 void LibcurlHttpFetcher::ResumeTransfer(const std::string& url) { |
| 22 LOG(INFO) << "Starting/Resuming transfer"; |
22 CHECK(!transfer_in_progress_); | 23 CHECK(!transfer_in_progress_); |
23 url_ = url; | 24 url_ = url; |
24 curl_multi_handle_ = curl_multi_init(); | 25 curl_multi_handle_ = curl_multi_init(); |
25 CHECK(curl_multi_handle_); | 26 CHECK(curl_multi_handle_); |
26 | 27 |
27 curl_handle_ = curl_easy_init(); | 28 curl_handle_ = curl_easy_init(); |
28 CHECK(curl_handle_); | 29 CHECK(curl_handle_); |
29 | 30 |
30 if (post_data_set_) { | 31 if (post_data_set_) { |
31 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POST, 1), CURLE_OK); | 32 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POST, 1), CURLE_OK); |
(...skipping 10 matching lines...) Expand all Loading... |
42 resume_offset_ = bytes_downloaded_; | 43 resume_offset_ = bytes_downloaded_; |
43 CHECK_EQ(curl_easy_setopt(curl_handle_, | 44 CHECK_EQ(curl_easy_setopt(curl_handle_, |
44 CURLOPT_RESUME_FROM_LARGE, | 45 CURLOPT_RESUME_FROM_LARGE, |
45 bytes_downloaded_), CURLE_OK); | 46 bytes_downloaded_), CURLE_OK); |
46 } | 47 } |
47 | 48 |
48 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, this), CURLE_OK); | 49 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, this), CURLE_OK); |
49 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEFUNCTION, | 50 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEFUNCTION, |
50 StaticLibcurlWrite), CURLE_OK); | 51 StaticLibcurlWrite), CURLE_OK); |
51 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_URL, url_.c_str()), CURLE_OK); | 52 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_URL, url_.c_str()), CURLE_OK); |
| 53 |
| 54 // If the connection drops under 10 bytes/sec for 90 seconds, reconnect. |
| 55 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_LOW_SPEED_LIMIT, 10), |
| 56 CURLE_OK); |
| 57 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_LOW_SPEED_TIME, 90), |
| 58 CURLE_OK); |
| 59 |
52 CHECK_EQ(curl_multi_add_handle(curl_multi_handle_, curl_handle_), CURLM_OK); | 60 CHECK_EQ(curl_multi_add_handle(curl_multi_handle_, curl_handle_), CURLM_OK); |
53 transfer_in_progress_ = true; | 61 transfer_in_progress_ = true; |
54 CurlPerformOnce(); | |
55 } | 62 } |
56 | 63 |
57 // Begins the transfer, which must not have already been started. | 64 // Begins the transfer, which must not have already been started. |
58 void LibcurlHttpFetcher::BeginTransfer(const std::string& url) { | 65 void LibcurlHttpFetcher::BeginTransfer(const std::string& url) { |
59 transfer_size_ = -1; | 66 transfer_size_ = -1; |
60 bytes_downloaded_ = 0; | 67 bytes_downloaded_ = 0; |
61 resume_offset_ = 0; | 68 resume_offset_ = 0; |
62 ResumeTransfer(url); | 69 do { |
| 70 ResumeTransfer(url); |
| 71 } while (CurlPerformOnce()); |
63 } | 72 } |
64 | 73 |
65 void LibcurlHttpFetcher::TerminateTransfer() { | 74 void LibcurlHttpFetcher::TerminateTransfer() { |
66 CleanUp(); | 75 CleanUp(); |
67 } | 76 } |
68 | 77 |
69 // TODO(adlr): detect network failures | 78 bool LibcurlHttpFetcher::CurlPerformOnce() { |
70 void LibcurlHttpFetcher::CurlPerformOnce() { | |
71 CHECK(transfer_in_progress_); | 79 CHECK(transfer_in_progress_); |
72 int running_handles = 0; | 80 int running_handles = 0; |
73 CURLMcode retcode = CURLM_CALL_MULTI_PERFORM; | 81 CURLMcode retcode = CURLM_CALL_MULTI_PERFORM; |
74 | 82 |
75 // libcurl may request that we immediately call curl_multi_perform after it | 83 // libcurl may request that we immediately call curl_multi_perform after it |
76 // returns, so we do. libcurl promises that curl_multi_perform will not block. | 84 // returns, so we do. libcurl promises that curl_multi_perform will not block. |
77 while (CURLM_CALL_MULTI_PERFORM == retcode) { | 85 while (CURLM_CALL_MULTI_PERFORM == retcode) { |
78 retcode = curl_multi_perform(curl_multi_handle_, &running_handles); | 86 retcode = curl_multi_perform(curl_multi_handle_, &running_handles); |
79 } | 87 } |
80 if (0 == running_handles) { | 88 if (0 == running_handles) { |
81 // we're done! | 89 // we're done! |
82 CleanUp(); | 90 CleanUp(); |
83 | 91 |
84 if ((transfer_size_ >= 0) && (bytes_downloaded_ < transfer_size_)) { | 92 if ((transfer_size_ >= 0) && (bytes_downloaded_ < transfer_size_)) { |
85 ResumeTransfer(url_); | 93 // Need to restart transfer |
| 94 return true; |
86 } else { | 95 } else { |
87 if (delegate_) { | 96 if (delegate_) { |
88 delegate_->TransferComplete(this, true); // success | 97 delegate_->TransferComplete(this, true); // success |
89 } | 98 } |
90 } | 99 } |
91 } else { | 100 } else { |
92 // set up callback | 101 // set up callback |
93 SetupMainloopSources(); | 102 SetupMainloopSources(); |
94 } | 103 } |
| 104 return false; |
95 } | 105 } |
96 | 106 |
97 size_t LibcurlHttpFetcher::LibcurlWrite(void *ptr, size_t size, size_t nmemb) { | 107 size_t LibcurlHttpFetcher::LibcurlWrite(void *ptr, size_t size, size_t nmemb) { |
98 { | 108 { |
99 double transfer_size_double; | 109 double transfer_size_double; |
100 CHECK_EQ(curl_easy_getinfo(curl_handle_, | 110 CHECK_EQ(curl_easy_getinfo(curl_handle_, |
101 CURLINFO_CONTENT_LENGTH_DOWNLOAD, | 111 CURLINFO_CONTENT_LENGTH_DOWNLOAD, |
102 &transfer_size_double), CURLE_OK); | 112 &transfer_size_double), CURLE_OK); |
103 off_t new_transfer_size = static_cast<off_t>(transfer_size_double); | 113 off_t new_transfer_size = static_cast<off_t>(transfer_size_double); |
104 if (new_transfer_size > 0) { | 114 if (new_transfer_size > 0) { |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
156 if (io_channels_.find(i) != io_channels_.end()) { | 166 if (io_channels_.find(i) != io_channels_.end()) { |
157 g_source_remove(io_channels_[i].second); | 167 g_source_remove(io_channels_[i].second); |
158 g_io_channel_unref(io_channels_[i].first); | 168 g_io_channel_unref(io_channels_[i].first); |
159 io_channels_.erase(io_channels_.find(i)); | 169 io_channels_.erase(io_channels_.find(i)); |
160 } | 170 } |
161 continue; | 171 continue; |
162 } | 172 } |
163 // If we are already tracking this fd, continue. | 173 // If we are already tracking this fd, continue. |
164 if (io_channels_.find(i) != io_channels_.end()) | 174 if (io_channels_.find(i) != io_channels_.end()) |
165 continue; | 175 continue; |
166 | |
167 // We must track a new fd | 176 // We must track a new fd |
168 GIOChannel *io_channel = g_io_channel_unix_new(i); | 177 GIOChannel *io_channel = g_io_channel_unix_new(i); |
169 guint tag = g_io_add_watch( | 178 guint tag = g_io_add_watch( |
170 io_channel, | 179 io_channel, |
171 static_cast<GIOCondition>(G_IO_IN | G_IO_OUT | G_IO_PRI | | 180 static_cast<GIOCondition>(G_IO_IN | G_IO_OUT | G_IO_PRI | |
172 G_IO_ERR | G_IO_HUP), | 181 G_IO_ERR | G_IO_HUP), |
173 &StaticFDCallback, | 182 &StaticFDCallback, |
174 this); | 183 this); |
175 io_channels_[i] = make_pair(io_channel, tag); | 184 io_channels_[i] = make_pair(io_channel, tag); |
| 185 static int io_counter = 0; |
| 186 io_counter++; |
| 187 if (io_counter % 50 == 0) { |
| 188 LOG(INFO) << "io_counter = " << io_counter; |
| 189 } |
176 } | 190 } |
177 | 191 |
178 // Wet up a timeout callback for libcurl | 192 // Wet up a timeout callback for libcurl |
179 long ms = 0; | 193 long ms = 0; |
180 CHECK_EQ(curl_multi_timeout(curl_multi_handle_, &ms), CURLM_OK); | 194 CHECK_EQ(curl_multi_timeout(curl_multi_handle_, &ms), CURLM_OK); |
181 if (ms < 0) { | 195 if (ms < 0) { |
182 // From http://curl.haxx.se/libcurl/c/curl_multi_timeout.html: | 196 // From http://curl.haxx.se/libcurl/c/curl_multi_timeout.html: |
183 // if libcurl returns a -1 timeout here, it just means that libcurl | 197 // if libcurl returns a -1 timeout here, it just means that libcurl |
184 // currently has no stored timeout value. You must not wait too long | 198 // currently has no stored timeout value. You must not wait too long |
185 // (more than a few seconds perhaps) before you call | 199 // (more than a few seconds perhaps) before you call |
186 // curl_multi_perform() again. | 200 // curl_multi_perform() again. |
187 ms = idle_ms_; | 201 ms = idle_ms_; |
188 } | 202 } |
189 if (timeout_source_) { | 203 if (!timeout_source_) { |
190 g_source_destroy(timeout_source_); | 204 LOG(INFO) << "setting up timeout source:" << ms; |
191 timeout_source_ = NULL; | 205 timeout_source_ = g_timeout_source_new(1000); |
| 206 CHECK(timeout_source_); |
| 207 g_source_set_callback(timeout_source_, StaticTimeoutCallback, this, |
| 208 NULL); |
| 209 g_source_attach(timeout_source_, NULL); |
| 210 static int counter = 0; |
| 211 counter++; |
| 212 if (counter % 50 == 0) { |
| 213 LOG(INFO) << "counter = " << counter; |
| 214 } |
192 } | 215 } |
193 timeout_source_ = g_timeout_source_new(ms); | |
194 CHECK(timeout_source_); | |
195 g_source_set_callback(timeout_source_, StaticTimeoutCallback, this, | |
196 NULL); | |
197 g_source_attach(timeout_source_, NULL); | |
198 } | 216 } |
199 | 217 |
200 bool LibcurlHttpFetcher::FDCallback(GIOChannel *source, | 218 bool LibcurlHttpFetcher::FDCallback(GIOChannel *source, |
201 GIOCondition condition) { | 219 GIOCondition condition) { |
202 // Figure out which source it was; hopefully there aren't too many b/c | 220 while (CurlPerformOnce()) { |
203 // this is a linear scan of our channels | 221 ResumeTransfer(url_); |
204 bool found_in_set = false; | |
205 for (IOChannels::iterator it = io_channels_.begin(); | |
206 it != io_channels_.end(); ++it) { | |
207 if (it->second.first == source) { | |
208 // We will return false from this method, meaning that we shouldn't keep | |
209 // this g_io_channel around. So we remove it now from our collection of | |
210 // g_io_channels so that the other code in this class doens't mess with | |
211 // this (doomed) GIOChannel. | |
212 // TODO(adlr): optimize by seeing if we should reuse this GIOChannel | |
213 g_source_remove(it->second.second); | |
214 g_io_channel_unref(it->second.first); | |
215 io_channels_.erase(it); | |
216 found_in_set = true; | |
217 break; | |
218 } | |
219 } | 222 } |
220 CHECK(found_in_set); | 223 // We handle removing of this source elsewhere, so we always return true. |
221 CurlPerformOnce(); | 224 // The docs say, "the function should return FALSE if the event source |
222 return false; | 225 // should be removed." |
| 226 // http://www.gtk.org/api/2.6/glib/glib-IO-Channels.html#GIOFunc |
| 227 return true; |
223 } | 228 } |
224 | 229 |
225 bool LibcurlHttpFetcher::TimeoutCallback() { | 230 gboolean LibcurlHttpFetcher::TimeoutCallback() { |
| 231 if (!transfer_in_progress_) |
| 232 return TRUE; |
226 // Since we will return false from this function, which tells glib to | 233 // Since we will return false from this function, which tells glib to |
227 // destroy the timeout callback, we must NULL it out here. This way, when | 234 // destroy the timeout callback, we must NULL it out here. This way, when |
228 // setting up callback sources again, we won't try to delete this (doomed) | 235 // setting up callback sources again, we won't try to delete this (doomed) |
229 // timeout callback then. | 236 // timeout callback then. |
230 // TODO(adlr): optimize by checking if we can keep this timeout callback. | 237 // TODO(adlr): optimize by checking if we can keep this timeout callback. |
231 timeout_source_ = NULL; | 238 //timeout_source_ = NULL; |
232 CurlPerformOnce(); | 239 while (CurlPerformOnce()) { |
233 return false; | 240 ResumeTransfer(url_); |
| 241 } |
| 242 return TRUE; |
234 } | 243 } |
235 | 244 |
236 void LibcurlHttpFetcher::CleanUp() { | 245 void LibcurlHttpFetcher::CleanUp() { |
237 if (timeout_source_) { | 246 if (timeout_source_) { |
238 g_source_destroy(timeout_source_); | 247 g_source_destroy(timeout_source_); |
239 timeout_source_ = NULL; | 248 timeout_source_ = NULL; |
240 } | 249 } |
241 | 250 |
242 for (IOChannels::iterator it = io_channels_.begin(); | 251 for (IOChannels::iterator it = io_channels_.begin(); |
243 it != io_channels_.end(); ++it) { | 252 it != io_channels_.end(); ++it) { |
(...skipping 11 matching lines...) Expand all Loading... |
255 curl_handle_ = NULL; | 264 curl_handle_ = NULL; |
256 } | 265 } |
257 if (curl_multi_handle_) { | 266 if (curl_multi_handle_) { |
258 CHECK_EQ(curl_multi_cleanup(curl_multi_handle_), CURLM_OK); | 267 CHECK_EQ(curl_multi_cleanup(curl_multi_handle_), CURLM_OK); |
259 curl_multi_handle_ = NULL; | 268 curl_multi_handle_ = NULL; |
260 } | 269 } |
261 transfer_in_progress_ = false; | 270 transfer_in_progress_ = false; |
262 } | 271 } |
263 | 272 |
264 } // namespace chromeos_update_engine | 273 } // namespace chromeos_update_engine |
OLD | NEW |