~ubuntu-branches/ubuntu/wily/libtorrent/wily-proposed

« back to all changes in this revision

Viewing changes to src/data/chunk_list.cc

  • Committer: Bazaar Package Importer
  • Author(s): Rogério Brito
  • Date: 2011-03-20 01:06:18 UTC
  • mfrom: (1.1.13 upstream) (4.1.9 sid)
  • Revision ID: james.westby@ubuntu.com-20110320010618-g3wyylccqzqko73c
Tags: 0.12.7-5
* Use Steinar's "real" patch for IPv6. Addresses #490277, #618275,
  and Closes: #617791.
* Adapt libtorrent-0.12.6-ipv6-07.patch. It FTBFS otherwise.
* Add proper attibution to the IPv6 patch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// libTorrent - BitTorrent library
 
2
// Copyright (C) 2005-2007, Jari Sundell
 
3
//
 
4
// This program is free software; you can redistribute it and/or modify
 
5
// it under the terms of the GNU General Public License as published by
 
6
// the Free Software Foundation; either version 2 of the License, or
 
7
// (at your option) any later version.
 
8
// 
 
9
// This program is distributed in the hope that it will be useful,
 
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
// GNU General Public License for more details.
 
13
// 
 
14
// You should have received a copy of the GNU General Public License
 
15
// along with this program; if not, write to the Free Software
 
16
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
17
//
 
18
// In addition, as a special exception, the copyright holders give
 
19
// permission to link the code of portions of this program with the
 
20
// OpenSSL library under certain conditions as described in each
 
21
// individual source file, and distribute linked combinations
 
22
// including the two.
 
23
//
 
24
// You must obey the GNU General Public License in all respects for
 
25
// all of the code used other than OpenSSL.  If you modify file(s)
 
26
// with this exception, you may extend this exception to your version
 
27
// of the file(s), but you are not obligated to do so.  If you do not
 
28
// wish to do so, delete this exception statement from your version.
 
29
// If you delete this exception statement from all source files in the
 
30
// program, then also delete it here.
 
31
//
 
32
// Contact:  Jari Sundell <jaris@ifi.uio.no>
 
33
//
 
34
//           Skomakerveien 33
 
35
//           3185 Skoppum, NORWAY
 
36
 
 
37
#include "config.h"
 
38
 
 
39
#include "torrent/exceptions.h"
 
40
#include "torrent/chunk_manager.h"
 
41
 
 
42
#include "chunk_list.h"
 
43
#include "chunk.h"
 
44
#include "globals.h"
 
45
 
 
46
namespace torrent {
 
47
 
 
48
struct chunk_list_earliest_modified {
 
49
  chunk_list_earliest_modified() : m_time(cachedTime) {}
 
50
 
 
51
  void operator () (ChunkListNode* node) {
 
52
    if (node->time_modified() < m_time && node->time_modified() != rak::timer())
 
53
      m_time = node->time_modified();
 
54
  }
 
55
 
 
56
  rak::timer m_time;
 
57
};
 
58
 
 
59
struct chunk_list_sort_index {
 
60
  bool operator () (ChunkListNode* node1, ChunkListNode* node2) {
 
61
    return node1->index() < node2->index();
 
62
  }
 
63
};
 
64
 
 
65
inline bool
 
66
ChunkList::is_queued(ChunkListNode* node) {
 
67
  return std::find(m_queue.begin(), m_queue.end(), node) != m_queue.end();
 
68
}
 
69
 
 
70
bool
 
71
ChunkList::has_chunk(size_type index, int prot) const {
 
72
  return base_type::at(index).is_valid() && base_type::at(index).chunk()->has_permissions(prot);
 
73
}
 
74
 
 
75
void
 
76
ChunkList::resize(size_type s) {
 
77
  if (!empty())
 
78
    throw internal_error("ChunkList::resize(...) called on an non-empty object.");
 
79
 
 
80
  base_type::resize(s);
 
81
 
 
82
  uint32_t index = 0;
 
83
 
 
84
  for (iterator itr = begin(), last = end(); itr != last; ++itr, ++index)
 
85
    itr->set_index(index);
 
86
}
 
87
 
 
88
void
 
89
ChunkList::clear() {
 
90
  // Don't do any sync'ing as whomever decided to shut down really
 
91
  // doesn't care, so just de-reference all chunks in queue.
 
92
  for (Queue::iterator itr = m_queue.begin(), last = m_queue.end(); itr != last; ++itr) {
 
93
    if ((*itr)->references() != 1 || (*itr)->writable() != 1)
 
94
      throw internal_error("ChunkList::clear() called but a node in the queue is still referenced.");
 
95
    
 
96
    (*itr)->dec_rw();
 
97
    clear_chunk(*itr);
 
98
  }
 
99
 
 
100
  m_queue.clear();
 
101
 
 
102
  if (std::find_if(begin(), end(), std::mem_fun_ref(&ChunkListNode::chunk)) != end() ||
 
103
      std::find_if(begin(), end(), std::mem_fun_ref(&ChunkListNode::references)) != end() ||
 
104
      std::find_if(begin(), end(), std::mem_fun_ref(&ChunkListNode::writable)) != end())
 
105
    throw internal_error("ChunkList::clear() called but a valid node was found.");
 
106
 
 
107
  base_type::clear();
 
108
}
 
109
 
 
110
ChunkHandle
 
111
ChunkList::get(size_type index, bool writable) {
 
112
  rak::error_number::clear_global();
 
113
 
 
114
  ChunkListNode* node = &base_type::at(index);
 
115
 
 
116
  if (!node->is_valid()) {
 
117
    Chunk* chunk = m_slotCreateChunk(index, MemoryChunk::prot_read | (writable ? MemoryChunk::prot_write : 0));
 
118
 
 
119
    if (chunk == NULL)
 
120
      return ChunkHandle::from_error(rak::error_number::current().is_valid() ? rak::error_number::current() : rak::error_number::e_noent);
 
121
 
 
122
    // Would be cleaner to do this before creating the chunk.
 
123
    if (!m_manager->allocate(chunk->chunk_size())) {
 
124
      delete chunk;
 
125
      return ChunkHandle::from_error(rak::error_number::e_nomem);
 
126
    }
 
127
 
 
128
    node->set_chunk(chunk);
 
129
    node->set_time_modified(rak::timer());
 
130
 
 
131
  } else if (writable && !node->chunk()->is_writable()) {
 
132
    Chunk* chunk = m_slotCreateChunk(index, MemoryChunk::prot_read | (writable ? MemoryChunk::prot_write : 0));
 
133
 
 
134
    if (chunk == NULL)
 
135
      return ChunkHandle::from_error(rak::error_number::current().is_valid() ? rak::error_number::current() : rak::error_number::e_noent);
 
136
 
 
137
    delete node->chunk();
 
138
 
 
139
    node->set_chunk(chunk);
 
140
    node->set_time_modified(rak::timer());
 
141
  }
 
142
 
 
143
  node->inc_references();
 
144
 
 
145
  if (writable) {
 
146
    node->inc_writable();
 
147
 
 
148
    // Make sure that periodic syncing uses async on any subsequent
 
149
    // changes even if it was triggered before this get.
 
150
    node->set_sync_triggered(false);
 
151
  }
 
152
 
 
153
  return ChunkHandle(node, writable);
 
154
}
 
155
 
 
156
// The chunks in 'm_queue' have been modified and need to be synced
 
157
// when appropriate. Hopefully keeping the chunks mmap'ed for a while
 
158
// will allow us to schedule writes at more resonable intervals.
 
159
 
 
160
void
 
161
ChunkList::release(ChunkHandle* handle) {
 
162
  if (!handle->is_valid())
 
163
    throw internal_error("ChunkList::release(...) received an invalid handle.");
 
164
 
 
165
  if (handle->object() < &*begin() || handle->object() >= &*end())
 
166
    throw internal_error("ChunkList::release(...) received an unknown handle.");
 
167
 
 
168
  if (handle->object()->references() <= 0 || (handle->is_writable() && handle->object()->writable() <= 0))
 
169
    throw internal_error("ChunkList::release(...) received a node with bad reference count.");
 
170
 
 
171
  if (handle->is_writable()) {
 
172
 
 
173
    if (handle->object()->writable() == 1) {
 
174
      if (is_queued(handle->object()))
 
175
        throw internal_error("ChunkList::release(...) tried to queue an already queued chunk.");
 
176
 
 
177
      // Only add those that have a modification time set?
 
178
      //
 
179
      // Only chunks that are not already in the queue will execute
 
180
      // this branch.
 
181
      m_queue.push_back(handle->object());
 
182
 
 
183
    } else {
 
184
      handle->object()->dec_rw();
 
185
    }
 
186
 
 
187
  } else {
 
188
    if (handle->object()->dec_references() == 0) {
 
189
      if (is_queued(handle->object()))
 
190
        throw internal_error("ChunkList::release(...) tried to unmap a queued chunk.");
 
191
 
 
192
      clear_chunk(handle->object());
 
193
    }
 
194
  }
 
195
 
 
196
  handle->clear();
 
197
}
 
198
 
 
199
void
 
200
ChunkList::clear_chunk(ChunkListNode* node) {
 
201
  if (!node->is_valid())
 
202
    throw internal_error("ChunkList::clear_chunk(...) !node->is_valid().");
 
203
 
 
204
  uint32_t size = node->chunk()->chunk_size();
 
205
 
 
206
  delete node->chunk();
 
207
  node->set_chunk(NULL);
 
208
 
 
209
  m_manager->deallocate(size);
 
210
}
 
211
 
 
212
inline bool
 
213
ChunkList::sync_chunk(ChunkListNode* node, std::pair<int,bool> options) {
 
214
  if (node->references() <= 0 || node->writable() <= 0)
 
215
    throw internal_error("ChunkList::sync_chunk(...) got a node with invalid reference count.");
 
216
 
 
217
  if (!node->chunk()->sync(options.first))
 
218
    return false;
 
219
 
 
220
  node->set_sync_triggered(true);
 
221
 
 
222
  if (!options.second)
 
223
    return true;
 
224
 
 
225
  node->dec_rw();
 
226
 
 
227
  if (node->references() == 0)
 
228
    clear_chunk(node);
 
229
 
 
230
  return true;
 
231
}
 
232
 
 
233
uint32_t
 
234
ChunkList::sync_chunks(int flags) {
 
235
  Queue::iterator split;
 
236
 
 
237
  if (flags & sync_all)
 
238
    split = m_queue.begin();
 
239
  else
 
240
    split = std::stable_partition(m_queue.begin(), m_queue.end(), rak::not_equal(1, std::mem_fun(&ChunkListNode::writable)));
 
241
 
 
242
  // Allow a flag that does more culling, so that we only get large
 
243
  // continous sections.
 
244
  //
 
245
  // How does this interact with timers, should be make it so that
 
246
  // only areas with timers are (preferably) synced?
 
247
 
 
248
  std::sort(split, m_queue.end());
 
249
  
 
250
  // If we got enough diskspace and have not requested safe syncing,
 
251
  // then sync all chunks with MS_ASYNC.
 
252
  if (!(flags & (sync_safe | sync_sloppy))) {
 
253
    if (m_manager->safe_sync() || m_slotFreeDiskspace() <= m_manager->safe_free_diskspace())
 
254
      flags |= sync_safe;
 
255
    else
 
256
      flags |= sync_force;
 
257
  }
 
258
 
 
259
  // TODO: This won't trigger for default sync_force.
 
260
  if ((flags & sync_use_timeout) && !(flags & sync_force))
 
261
    split = partition_optimize(split, m_queue.end(), 50, 5, false);
 
262
 
 
263
  uint32_t failed = 0;
 
264
 
 
265
  for (Queue::iterator itr = split, last = m_queue.end(); itr != last; ++itr) {
 
266
    
 
267
    // We can easily skip pieces by swap_iter, so there should be no
 
268
    // problem being selective about the ranges we sync.
 
269
 
 
270
    // Use a function for checking the next few chunks and see how far
 
271
    // we want to sync. When we want to sync everything use end. Call
 
272
    // before the loop, or add a check.
 
273
 
 
274
    // if we don't want to sync, swap and break.
 
275
 
 
276
    std::pair<int,bool> options = sync_options(*itr, flags);
 
277
 
 
278
    if (!sync_chunk(*itr, options)) {
 
279
      std::iter_swap(itr, split++);
 
280
      
 
281
      failed++;
 
282
      continue;
 
283
    }
 
284
 
 
285
    (*itr)->set_sync_triggered(true);
 
286
 
 
287
    if (!options.second)
 
288
      std::iter_swap(itr, split++);
 
289
  }
 
290
 
 
291
  m_queue.erase(split, m_queue.end());
 
292
 
 
293
  // The caller must either make sure that it is safe to close the
 
294
  // download or set the sync_ignore_error flag.
 
295
  if (failed && !(flags & sync_ignore_error))
 
296
    m_slotStorageError("Could not sync chunk: " + std::string(rak::error_number::current().c_str()));
 
297
 
 
298
  return failed;
 
299
}
 
300
 
 
301
std::pair<int, bool>
 
302
ChunkList::sync_options(ChunkListNode* node, int flags) {
 
303
  // Using if statements since some linkers have problem with static
 
304
  // const int members inside the ?: operators. The compiler should
 
305
  // be optimizing this anyway.
 
306
 
 
307
  if (flags & sync_force) {
 
308
 
 
309
    if (flags & sync_safe)
 
310
      return std::make_pair(MemoryChunk::sync_sync, true);
 
311
    else
 
312
      return std::make_pair(MemoryChunk::sync_async, true);
 
313
 
 
314
  } else if (flags & sync_safe) {
 
315
      
 
316
    if (node->sync_triggered())
 
317
      return std::make_pair(MemoryChunk::sync_sync, true);
 
318
    else
 
319
      return std::make_pair(MemoryChunk::sync_async, false);
 
320
 
 
321
  } else {
 
322
    return std::make_pair(MemoryChunk::sync_async, true);
 
323
  }
 
324
}
 
325
 
 
326
// Using a rather simple algorithm for now. This should really be more
 
327
// robust against holes withing otherwise compact ranges and take into
 
328
// consideration chunk size.
 
329
inline ChunkList::Queue::iterator
 
330
ChunkList::seek_range(Queue::iterator first, Queue::iterator last) {
 
331
  uint32_t prevIndex = (*first)->index();
 
332
 
 
333
  while (++first != last) {
 
334
    if ((*first)->index() - prevIndex > 5)
 
335
      break;
 
336
 
 
337
    prevIndex = (*first)->index();
 
338
  }
 
339
 
 
340
  return first;
 
341
}
 
342
 
 
343
inline bool
 
344
ChunkList::check_node(ChunkListNode* node) {
 
345
  return
 
346
    node->time_modified() != rak::timer() &&
 
347
    node->time_modified() + rak::timer::from_seconds(m_manager->timeout_sync()) < cachedTime;
 
348
}
 
349
 
 
350
// Optimize the selection of chunks to sync. Continuous regions are
 
351
// preferred, while if too fragmented or if too few chunks are
 
352
// available it skips syncing of all chunks.
 
353
 
 
354
ChunkList::Queue::iterator
 
355
ChunkList::partition_optimize(Queue::iterator first, Queue::iterator last, int weight, int maxDistance, bool dontSkip) {
 
356
  for (Queue::iterator itr = first; itr != last;) {
 
357
    Queue::iterator range = seek_range(itr, last);
 
358
 
 
359
    bool required = std::find_if(itr, range, std::bind1st(std::mem_fun(&ChunkList::check_node), this)) != range;
 
360
    dontSkip = dontSkip || required;
 
361
 
 
362
    if (!required && std::distance(itr, range) < maxDistance) {
 
363
      // Don't sync this range.
 
364
      unsigned int l = std::min(range - itr, itr - first);
 
365
      std::swap_ranges(first, first + l, range - l);
 
366
 
 
367
      first += l;
 
368
 
 
369
    } else {
 
370
      // This probably increases too fast.
 
371
      weight -= std::distance(itr, range) * std::distance(itr, range);
 
372
    }
 
373
 
 
374
    itr = range;
 
375
  }
 
376
 
 
377
  // These values are all arbritrary...
 
378
  if (!dontSkip && weight > 0)
 
379
    return last;
 
380
 
 
381
  return first;
 
382
}
 
383
 
 
384
}