comparison data/fileio/MatrixFile.cpp @ 0:fc9323a41f5a

start base : Sonic Visualiser sv1-1.0rc1
author lbajardsilogic
date Fri, 11 May 2007 09:08:14 +0000
parents
children 61681a2bc1e6
comparison
equal deleted inserted replaced
-1:000000000000 0:fc9323a41f5a
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2
3 /*
4 Sonic Visualiser
5 An audio file viewer and annotation editor.
6 Centre for Digital Music, Queen Mary, University of London.
7 This file copyright 2006 Chris Cannam.
8
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License as
11 published by the Free Software Foundation; either version 2 of the
12 License, or (at your option) any later version. See the file
13 COPYING included with this distribution for more information.
14 */
15
16 #include "MatrixFile.h"
17 #include "base/TempDirectory.h"
18 #include "system/System.h"
19 #include "base/Profiler.h"
20 #include "base/Exceptions.h"
21
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <fcntl.h>
25 //#include <unistd.h>
26
27 #include <iostream>
28
29 #include <cstdio>
30 #include <cassert>
31
32 #include <QFileInfo>
33 #include <QDir>
34
35 //#define DEBUG_MATRIX_FILE 1
36 //#define DEBUG_MATRIX_FILE_READ_SET 1
37
38 #ifdef DEBUG_MATRIX_FILE_READ_SET
39 #ifndef DEBUG_MATRIX_FILE
40 #define DEBUG_MATRIX_FILE 1
41 #endif
42 #endif
43
44 std::map<QString, int> MatrixFile::m_refcount;
45 QMutex MatrixFile::m_refcountMutex;
46
47 MatrixFile::ResizeableBitsetMap MatrixFile::m_columnBitsets;
48 QMutex MatrixFile::m_columnBitsetWriteMutex;
49
50 FileReadThread *MatrixFile::m_readThread = 0;
51
52 static size_t totalStorage = 0;
53 static size_t totalMemory = 0;
54 static size_t totalCount = 0;
55
56 MatrixFile::MatrixFile(QString fileBase, Mode mode,
57 size_t cellSize, bool eagerCache) :
58 m_fd(-1),
59 m_mode(mode),
60 m_flags(0),
61 m_fmode(0),
62 m_cellSize(cellSize),
63 m_width(0),
64 m_height(0),
65 m_headerSize(2 * sizeof(size_t)),
66 m_defaultCacheWidth(1024),
67 m_prevX(0),
68 m_eagerCache(eagerCache),
69 m_requestToken(-1),
70 m_spareData(0),
71 m_columnBitset(0)
72 {
73 Profiler profiler("MatrixFile::MatrixFile", true);
74
75 if (!m_readThread) {
76 m_readThread = new FileReadThread;
77 m_readThread->start();
78 }
79
80 m_cache.data = 0;
81
82 QDir tempDir(TempDirectory::getInstance()->getPath());
83 QString fileName(tempDir.filePath(QString("%1.mfc").arg(fileBase)));
84 bool newFile = !QFileInfo(fileName).exists();
85
86 if (newFile && m_mode == ReadOnly) {
87 std::cerr << "ERROR: MatrixFile::MatrixFile: Read-only mode "
88 << "specified, but cache file does not exist" << std::endl;
89 throw FileNotFound(fileName);
90 }
91
92 if (!newFile && m_mode == ReadWrite) {
93 std::cerr << "Note: MatrixFile::MatrixFile: Read/write mode "
94 << "specified, but file already exists; falling back to "
95 << "read-only mode" << std::endl;
96 m_mode = ReadOnly;
97 }
98
99 if (!eagerCache && m_mode == ReadOnly) {
100 std::cerr << "WARNING: MatrixFile::MatrixFile: Eager cacheing not "
101 << "specified, but file is open in read-only mode -- cache "
102 << "will not be used" << std::endl;
103 }
104
105 m_flags = 0;
106 m_fmode = S_IRUSR | S_IWUSR;
107
108 if (m_mode == ReadWrite) {
109 m_flags = O_RDWR | O_CREAT;
110 } else {
111 m_flags = O_RDONLY;
112 }
113
114 #ifdef _WIN32
115 m_flags |= O_BINARY;
116 #endif
117
118 #ifdef DEBUG_MATRIX_FILE
119 std::cerr << "MatrixFile::MatrixFile: opening " << fileName.toStdString() << "..." << std::endl;
120 #endif
121
122 if ((m_fd = ::open(fileName.toLocal8Bit(), m_flags, m_fmode)) < 0) {
123 ::perror("Open failed");
124 std::cerr << "ERROR: MatrixFile::MatrixFile: "
125 << "Failed to open cache file \""
126 << fileName.toStdString() << "\"";
127 if (m_mode == ReadWrite) std::cerr << " for writing";
128 std::cerr << std::endl;
129 throw FailedToOpenFile(fileName);
130 }
131
132 if (newFile) {
133 resize(0, 0); // write header
134 } else {
135 size_t header[2];
136 if (::read(m_fd, header, 2 * sizeof(size_t)) < 0) {
137 ::perror("MatrixFile::MatrixFile: read failed");
138 std::cerr << "ERROR: MatrixFile::MatrixFile: "
139 << "Failed to read header (fd " << m_fd << ", file \""
140 << fileName.toStdString() << "\")" << std::endl;
141 throw FileReadFailed(fileName);
142 }
143 m_width = header[0];
144 m_height = header[1];
145 seekTo(0, 0);
146 }
147
148 m_fileName = fileName;
149
150 m_columnBitsetWriteMutex.lock();
151
152 if (m_columnBitsets.find(m_fileName) == m_columnBitsets.end()) {
153 m_columnBitsets[m_fileName] = new ResizeableBitset;
154 }
155 m_columnBitset = m_columnBitsets[m_fileName];
156
157 m_columnBitsetWriteMutex.unlock();
158
159 QMutexLocker locker(&m_refcountMutex);
160 ++m_refcount[fileName];
161
162 // std::cerr << "MatrixFile(" << this << "): fd " << m_fd << ", file " << fileName.toStdString() << ", ref " << m_refcount[fileName] << std::endl;
163
164 // std::cerr << "MatrixFile::MatrixFile: Done, size is " << "(" << m_width << ", " << m_height << ")" << std::endl;
165
166 ++totalCount;
167
168 }
169
170 MatrixFile::~MatrixFile()
171 {
172 char *requestData = 0;
173
174 if (m_requestToken >= 0) {
175 FileReadThread::Request request;
176 if (m_readThread->getRequest(m_requestToken, request)) {
177 requestData = request.data;
178 }
179 m_readThread->cancel(m_requestToken);
180 }
181
182 if (requestData) free(requestData);
183 if (m_cache.data) free(m_cache.data);
184 if (m_spareData) free(m_spareData);
185
186 if (m_fd >= 0) {
187 if (::close(m_fd) < 0) {
188 ::perror("MatrixFile::~MatrixFile: close failed");
189 }
190 }
191
192 if (m_fileName != "") {
193
194 QMutexLocker locker(&m_refcountMutex);
195
196 if (--m_refcount[m_fileName] == 0) {
197
198 if (::unlink(m_fileName.toLocal8Bit())) {
199 // ::perror("Unlink failed");
200 // std::cerr << "WARNING: MatrixFile::~MatrixFile: reference count reached 0, but failed to unlink file \"" << m_fileName.toStdString() << "\"" << std::endl;
201 } else {
202 // std::cerr << "deleted " << m_fileName.toStdString() << std::endl;
203 }
204
205 QMutexLocker locker2(&m_columnBitsetWriteMutex);
206 m_columnBitsets.erase(m_fileName);
207 delete m_columnBitset;
208 }
209 }
210
211 totalStorage -= (m_headerSize + (m_width * m_height * m_cellSize));
212 totalMemory -= (2 * m_defaultCacheWidth * m_height * m_cellSize);
213 totalCount --;
214
215 // std::cerr << "MatrixFile::~MatrixFile: " << std::endl;
216 // std::cerr << "Total storage now " << totalStorage/1024 << "K, theoretical max memory "
217 // << totalMemory/1024 << "K in " << totalCount << " instances" << std::endl;
218
219 }
220
221 void
222 MatrixFile::resize(size_t w, size_t h)
223 {
224 Profiler profiler("MatrixFile::resize", true);
225
226 assert(m_mode == ReadWrite);
227
228 QMutexLocker locker(&m_fdMutex);
229
230 totalStorage -= (m_headerSize + (m_width * m_height * m_cellSize));
231 totalMemory -= (2 * m_defaultCacheWidth * m_height * m_cellSize);
232
233 off_t off = m_headerSize + (w * h * m_cellSize);
234
235 #ifdef DEBUG_MATRIX_FILE
236 std::cerr << "MatrixFile::resize(" << w << ", " << h << "): resizing file" << std::endl;
237 #endif
238
239 if (w * h < m_width * m_height) {
240 if (::ftruncate(m_fd, off) < 0) {
241 ::perror("WARNING: MatrixFile::resize: ftruncate failed");
242 throw FileOperationFailed(m_fileName, "ftruncate");
243 }
244 }
245
246 m_width = 0;
247 m_height = 0;
248
249 if (::lseek(m_fd, 0, SEEK_SET) == (off_t)-1) {
250 ::perror("ERROR: MatrixFile::resize: Seek to write header failed");
251 throw FileOperationFailed(m_fileName, "lseek");
252 }
253
254 size_t header[2];
255 header[0] = w;
256 header[1] = h;
257 if (::write(m_fd, header, 2 * sizeof(size_t)) != 2 * sizeof(size_t)) {
258 ::perror("ERROR: MatrixFile::resize: Failed to write header");
259 throw FileOperationFailed(m_fileName, "write");
260 }
261
262 if (w > 0 && m_defaultCacheWidth > w) {
263 m_defaultCacheWidth = w;
264 }
265
266 static size_t maxCacheMB = 16;
267 if (2 * m_defaultCacheWidth * h * m_cellSize > maxCacheMB * 1024 * 1024) { //!!!
268 m_defaultCacheWidth = (maxCacheMB * 1024 * 1024) / (2 * h * m_cellSize);
269 if (m_defaultCacheWidth < 16) m_defaultCacheWidth = 16;
270 }
271
272 if (m_columnBitset) {
273 QMutexLocker locker(&m_columnBitsetWriteMutex);
274 m_columnBitset->resize(w);
275 }
276
277 if (m_cache.data) {
278 free(m_cache.data);
279 m_cache.data = 0;
280 }
281
282 if (m_spareData) {
283 free(m_spareData);
284 m_spareData = 0;
285 }
286
287 m_width = w;
288 m_height = h;
289
290 totalStorage += (m_headerSize + (m_width * m_height * m_cellSize));
291 totalMemory += (2 * m_defaultCacheWidth * m_height * m_cellSize);
292
293 #ifdef DEBUG_MATRIX_FILE
294 std::cerr << "MatrixFile::resize(" << w << ", " << h << "): cache width "
295 << m_defaultCacheWidth << ", storage "
296 << (m_headerSize + w * h * m_cellSize) << ", mem "
297 << (2 * h * m_defaultCacheWidth * m_cellSize) << std::endl;
298
299 std::cerr << "Total storage " << totalStorage/1024 << "K, theoretical max memory "
300 << totalMemory/1024 << "K in " << totalCount << " instances" << std::endl;
301 #endif
302
303 seekTo(0, 0);
304 }
305
306 void
307 MatrixFile::reset()
308 {
309 Profiler profiler("MatrixFile::reset", true);
310
311 assert (m_mode == ReadWrite);
312
313 if (m_eagerCache) {
314 void *emptyCol = calloc(m_height, m_cellSize);
315 for (size_t x = 0; x < m_width; ++x) setColumnAt(x, emptyCol);
316 free(emptyCol);
317 }
318
319 if (m_columnBitset) {
320 QMutexLocker locker(&m_columnBitsetWriteMutex);
321 m_columnBitset->resize(m_width);
322 }
323 }
324
325 void
326 MatrixFile::getColumnAt(size_t x, void *data)
327 {
328 // Profiler profiler("MatrixFile::getColumnAt");
329
330 // assert(haveSetColumnAt(x));
331
332 if (getFromCache(x, 0, m_height, data)) return;
333
334 // Profiler profiler2("MatrixFile::getColumnAt (uncached)");
335
336 ssize_t r = 0;
337
338 #ifdef DEBUG_MATRIX_FILE
339 std::cerr << "MatrixFile::getColumnAt(" << x << ")"
340 << ": reading the slow way";
341
342 if (m_requestToken >= 0 &&
343 x >= m_requestingX &&
344 x < m_requestingX + m_requestingWidth) {
345
346 std::cerr << " (awaiting " << m_requestingX << ", " << m_requestingWidth << " from disk)";
347 }
348
349 std::cerr << std::endl;
350 #endif
351
352 m_fdMutex.lock();
353
354 if (seekTo(x, 0)) {
355 r = ::read(m_fd, data, m_height * m_cellSize);
356 }
357
358 m_fdMutex.unlock();
359
360 if (r < 0) {
361 ::perror("MatrixFile::getColumnAt: read failed");
362 std::cerr << "ERROR: MatrixFile::getColumnAt: "
363 << "Failed to read column " << x << " (height " << m_height << ", cell size " << m_cellSize << ", fd " << m_fd << ", file \""
364 << m_fileName.toStdString() << "\")" << std::endl;
365 throw FileReadFailed(m_fileName);
366 }
367
368 return;
369 }
370
371 bool
372 MatrixFile::getFromCache(size_t x, size_t ystart, size_t ycount, void *data)
373 {
374 m_cacheMutex.lock();
375
376 if (!m_cache.data || x < m_cache.x || x >= m_cache.x + m_cache.width) {
377 bool left = (m_cache.data && x < m_cache.x);
378 m_cacheMutex.unlock();
379 primeCache(x, left); // this doesn't take effect until a later callback
380 m_prevX = x;
381 return false;
382 }
383
384 memcpy(data,
385 m_cache.data + m_cellSize * ((x - m_cache.x) * m_height + ystart),
386 ycount * m_cellSize);
387
388 m_cacheMutex.unlock();
389
390 if (m_cache.x > 0 && x < m_prevX && x < m_cache.x + m_cache.width/4) {
391 primeCache(x, true);
392 }
393
394 if (m_cache.x + m_cache.width < m_width &&
395 x > m_prevX &&
396 x > m_cache.x + (m_cache.width * 3) / 4) {
397 primeCache(x, false);
398 }
399
400 m_prevX = x;
401 return true;
402 }
403
404 void
405 MatrixFile::setColumnAt(size_t x, const void *data)
406 {
407 assert(m_mode == ReadWrite);
408
409 #ifdef DEBUG_MATRIX_FILE_READ_SET
410 std::cerr << "MatrixFile::setColumnAt(" << x << ")" << std::endl;
411 #endif
412
413 ssize_t w = 0;
414 bool seekFailed = false;
415
416 m_fdMutex.lock();
417
418 if (seekTo(x, 0)) {
419 w = ::write(m_fd, data, m_height * m_cellSize);
420 } else {
421 seekFailed = true;
422 }
423
424 m_fdMutex.unlock();
425
426 if (!seekFailed && w != ssize_t(m_height * m_cellSize)) {
427 ::perror("WARNING: MatrixFile::setColumnAt: write failed");
428 throw FileOperationFailed(m_fileName, "write");
429 } else if (seekFailed) {
430 throw FileOperationFailed(m_fileName, "seek");
431 } else {
432 QMutexLocker locker(&m_columnBitsetWriteMutex);
433 m_columnBitset->set(x);
434 }
435 }
436
437 void
438 MatrixFile::suspend()
439 {
440 QMutexLocker locker(&m_fdMutex);
441 QMutexLocker locker2(&m_cacheMutex);
442
443 if (m_fd < 0) return; // already suspended
444
445 #ifdef DEBUG_MATRIX_FILE
446 std::cerr << "MatrixFile(" << this << ":" << m_fileName.toStdString() << ")::suspend(): fd was " << m_fd << std::endl;
447 #endif
448
449 if (m_requestToken >= 0) {
450 void *data = 0;
451 FileReadThread::Request request;
452 if (m_readThread->getRequest(m_requestToken, request)) {
453 data = request.data;
454 }
455 m_readThread->cancel(m_requestToken);
456 if (data) free(data);
457 m_requestToken = -1;
458 }
459
460 if (m_cache.data) {
461 free(m_cache.data);
462 m_cache.data = 0;
463 }
464
465 if (m_spareData) {
466 free(m_spareData);
467 m_spareData = 0;
468 }
469
470 if (::close(m_fd) < 0) {
471 ::perror("WARNING: MatrixFile::suspend: close failed");
472 throw FileOperationFailed(m_fileName, "close");
473 }
474
475 m_fd = -1;
476 }
477
478 void
479 MatrixFile::resume()
480 {
481 if (m_fd >= 0) return;
482
483 #ifdef DEBUG_MATRIX_FILE
484 std::cerr << "MatrixFile(" << this << ")::resume()" << std::endl;
485 #endif
486
487 if ((m_fd = ::open(m_fileName.toLocal8Bit(), m_flags, m_fmode)) < 0) {
488 ::perror("Open failed");
489 std::cerr << "ERROR: MatrixFile::resume: "
490 << "Failed to open cache file \""
491 << m_fileName.toStdString() << "\"";
492 if (m_mode == ReadWrite) std::cerr << " for writing";
493 std::cerr << std::endl;
494 throw FailedToOpenFile(m_fileName);
495 }
496
497 std::cerr << "MatrixFile(" << this << ":" << m_fileName.toStdString() << ")::resume(): fd is " << m_fd << std::endl;
498 }
499
500 void
501 MatrixFile::primeCache(size_t x, bool goingLeft)
502 {
503 // Profiler profiler("MatrixFile::primeCache");
504
505 #ifdef DEBUG_MATRIX_FILE_READ_SET
506 std::cerr << "MatrixFile::primeCache(" << x << ", " << goingLeft << ")" << std::endl;
507 #endif
508
509 size_t rx = x;
510 size_t rw = m_defaultCacheWidth;
511
512 size_t left = rw / 3;
513 if (goingLeft) left = (rw * 2) / 3;
514
515 if (rx > left) rx -= left;
516 else rx = 0;
517
518 if (rx + rw > m_width) rw = m_width - rx;
519
520 if (!m_eagerCache) {
521
522 size_t ti = 0;
523
524 for (ti = 0; ti < rw; ++ti) {
525 if (!m_columnBitset->get(rx + ti)) break;
526 }
527
528 #ifdef DEBUG_MATRIX_FILE
529 if (ti < rw) {
530 std::cerr << "eagerCache is false and there's a hole at "
531 << rx + ti << ", reducing rw from " << rw << " to "
532 << ti << std::endl;
533 }
534 #endif
535
536 rw = min(rw, ti);
537 if (rw < 10 || rx + rw <= x) return;
538 }
539
540 QMutexLocker locker(&m_cacheMutex);
541
542 FileReadThread::Request request;
543
544 if (m_requestToken >= 0 &&
545 m_readThread->getRequest(m_requestToken, request)) {
546
547 if (x >= m_requestingX &&
548 x < m_requestingX + m_requestingWidth) {
549
550 if (m_readThread->isReady(m_requestToken)) {
551
552 if (!request.successful) {
553 std::cerr << "ERROR: MatrixFile::primeCache: Last request was unsuccessful" << std::endl;
554 throw FileReadFailed(m_fileName);
555 }
556
557 #ifdef DEBUG_MATRIX_FILE_READ_SET
558 std::cerr << "last request is ready! (" << m_requestingX << ", "<< m_requestingWidth << ")" << std::endl;
559 #endif
560
561 m_cache.x = (request.start - m_headerSize) / (m_height * m_cellSize);
562 m_cache.width = request.size / (m_height * m_cellSize);
563
564 #ifdef DEBUG_MATRIX_FILE_READ_SET
565 std::cerr << "received last request: actual size is: " << m_cache.x << ", " << m_cache.width << std::endl;
566 #endif
567
568 if (m_cache.data) {
569 if (m_spareData) free(m_spareData);
570 m_spareData = m_cache.data;
571 }
572 m_cache.data = request.data;
573
574 m_readThread->done(m_requestToken);
575 m_requestToken = -1;
576 }
577
578 // already requested something covering this area; wait for it
579 return;
580 }
581
582 // the current request is no longer of any use
583 m_readThread->cancel(m_requestToken);
584
585 // crude way to avoid leaking the data
586 while (!m_readThread->isCancelled(m_requestToken)) {
587 usleep(10000);
588 }
589
590 #ifdef DEBUG_MATRIX_FILE_READ_SET
591 std::cerr << "cancelled " << m_requestToken << std::endl;
592 #endif
593
594 if (m_spareData) free(m_spareData);
595 m_spareData = request.data;
596 m_readThread->done(m_requestToken);
597
598 m_requestToken = -1;
599 }
600
601 if (m_fd < 0) {
602 m_fdMutex.lock();
603 if (m_fd < 0) resume();
604 m_fdMutex.unlock();
605 }
606
607 request.fd = m_fd;
608 request.mutex = &m_fdMutex;
609 request.start = m_headerSize + rx * m_height * m_cellSize;
610 request.size = rw * m_height * m_cellSize;
611 request.data = (char *)realloc(m_spareData, rw * m_height * m_cellSize);
612 MUNLOCK(request.data, rw * m_height * m_cellSize);
613 m_spareData = 0;
614
615 m_requestingX = rx;
616 m_requestingWidth = rw;
617
618 int token = m_readThread->request(request);
619 #ifdef DEBUG_MATRIX_FILE_READ_SET
620 std::cerr << "MatrixFile::primeCache: request token is "
621 << token << " (x = [" << rx << "], w = [" << rw << "], left = [" << goingLeft << "])" << std::endl;
622 #endif
623 m_requestToken = token;
624 }
625
626 bool
627 MatrixFile::seekTo(size_t x, size_t y)
628 {
629 if (m_fd < 0) resume();
630
631 off_t off = m_headerSize + (x * m_height + y) * m_cellSize;
632
633 if (::lseek(m_fd, off, SEEK_SET) == (off_t)-1) {
634 ::perror("Seek failed");
635 std::cerr << "ERROR: MatrixFile::seekTo(" << x << ", " << y
636 << ") failed" << std::endl;
637 return false;
638 }
639
640 return true;
641 }
642