[Amarok] b18a028: Use shared memory for collection scanner to rememb

Maximilian Kossick maximilian.kossick at googlemail.com
Sat Nov 6 10:38:55 CET 2010


One of the reasons for collection scanner as a separate executable was
to prevent a crash in collectionscanner from bringing down Amarok as
well. As both processes only communicated with each other via files or
stdout/stdin, this was relatively safe. Is it ensured that a
corrpution of the shared memory data does not crash both processes?

On Sat, Nov 6, 2010 at 4:05 AM, Ralf Engels <ralf-engels at gmx.de> wrote:
> commit b18a028c66b4aff2df1df9cf63fe1eb712a9b8bd
> branch master
> Author: Ralf Engels <ralf-engels at gmx.de>
> Date:   Fri Nov 5 19:14:32 2010 +0100
>
>    Use shared memory for collection scanner to remember last scanning position increasing the scanning speed.
>    Fix problem with track and stream resume
>
> diff --git a/src/core-impl/collections/db/ScanManager.cpp b/src/core-impl/collections/db/ScanManager.cpp
> index b02a80d..c145e08 100644
> --- a/src/core-impl/collections/db/ScanManager.cpp
> +++ b/src/core-impl/collections/db/ScanManager.cpp
> @@ -41,6 +41,7 @@
>  #include <QListIterator>
>  #include <QStringList>
>  #include <QTextCodec>
> +#include <QSharedMemory>
>
>  #include <KMessageBox>
>  #include <KStandardDirs>
> @@ -53,12 +54,14 @@ static const int MAX_RESTARTS = 80;
>  static const int MAX_FAILURE_PERCENTAGE = 5;
>  static const int WATCH_INTERVAL = 60 * 1000; // = 60 seconds
>
> +static const int SHARED_MEMORY_SIZE = 1024 * 1024; // 1 MB shared memory
>
>  ScanManager::ScanManager( Collections::DatabaseCollection *collection, QObject *parent )
>     : QObject( parent )
>     , m_collection( static_cast<Collections::SqlCollection*>( collection ) )
>     , m_scanner( 0 )
>     , m_parser( 0 )
> +    , m_scannerStateMemory( 0 )
>     , m_restartCount( 0 )
>     , m_blockCount( 0 )
>     , m_fullScanRequested( false )
> @@ -218,6 +221,20 @@ ScanManager::startScannerProcess( bool restart )
>     Q_ASSERT( m_parser );
>     Q_ASSERT( !m_scanner );
>
> +    // -- create the shared memory
> +    if( !m_scannerStateMemory && !restart )
> +    {
> +        m_sharedMemoryKey = "AmarokScannerMemory"+QDateTime::current().toString();
> +        m_scannerStateMemory = new QSharedMemory( m_sharedMemoryKey, this );
> +        if( !m_scannerStateMemory.create( SHARED_MEMORY_SIZE ) )
> +        {
> +            warning() << "Unable to create shared memory for collection scanner";
> +            delete m_scannerStateMemory;
> +            m_scannerStateMemory = 0;
> +        }
> +    }
> +
> +    // -- create the scanner process
>     m_scanner = new AmarokProcess( this );
>     *m_scanner << scannerPath() << "--idlepriority";
>     if( !m_fullScanRequested )
> @@ -232,6 +249,9 @@ ScanManager::startScannerProcess( bool restart )
>     if( restart )
>         *m_scanner << "-s";
>
> +    if( m_scannerStateMemory )
> +        *m_scanner << "--sharedmemory" << m_sharedMemoryKey;
> +
>     *m_scanner << "--batch" << m_batchfilePath;
>
>     m_scanner->setOutputChannelMode( KProcess::OnlyStdoutChannel );
> @@ -300,15 +320,14 @@ ScanManager::slotFinished(int exitCode, QProcess::ExitStatus exitStatus)
>
>     {
>         QMutexLocker locker( &m_mutex );
> -        m_scanner->terminate();
> -        m_scanner->deleteLater();
> -        m_scanner = 0;
> -        m_restartCount = 0;
> -
> -        m_fullScan = false;
> -        m_scanDirs.clear();
> +        if( m_scanner )
> +        {
> +            stopScanner();
>
> -        QFile::remove( m_batchfilePath );
> +            m_restartCount = 0;
> +            m_fullScan = false;
> +            m_scanDirs.clear();
> +        }
>     }
>  }
>
> @@ -337,15 +356,10 @@ ScanManager::abort( const QString &reason )
>         QMutexLocker locker( &m_mutex );
>         if( m_scanner )
>         {
> -            disconnect( m_scanner, 0, this, 0 );
> -            m_scanner->terminate();
> -            m_scanner->deleteLater();
> -            m_scanner = 0;
> +            stopScanner();
>
>             m_fullScan = false;
>             m_scanDirs.clear();
> -
> -            QFile::remove( m_batchfilePath );
>         }
>     }
>
> @@ -426,6 +440,23 @@ ScanManager::handleRestart()
>  void
>  ScanManager::stopParser()
>  {
> +    // stop the scanner
> +    disconnect( m_scanner, 0, this, 0 );
> +    m_scanner->terminate();
> +    m_scanner->deleteLater();
> +    m_scanner = 0;
> +
> +    // free it's shared memory.
> +    delete m_scannerStateMemory;
> +    m_scannerStateMemory = 0;
> +
> +    // remove the batch file
> +    QFile::remove( m_batchfilePath );
> +}
> +
> +void
> +ScanManager::stopParser()
> +{
>     QMutexLocker locker( &m_mutex );
>     if( m_parser )
>     {
> diff --git a/src/core-impl/collections/db/ScanManager.h b/src/core-impl/collections/db/ScanManager.h
> index 9fcdafd..4e20cbb 100644
> --- a/src/core-impl/collections/db/ScanManager.h
> +++ b/src/core-impl/collections/db/ScanManager.h
> @@ -35,6 +35,7 @@
>  #include <threadweaver/Job.h>
>
>  class XmlParseJob;
> +class QSharedMemory;
>
>  class AMAROK_DATABASECOLLECTION_EXPORT_TESTS ScanManager : public QObject
>  {
> @@ -110,12 +111,15 @@ class AMAROK_DATABASECOLLECTION_EXPORT_TESTS ScanManager : public QObject
>
>         void handleRestart();
>
> +        void stopScanner();
>         void stopParser();
>
>     private:
>         Collections::DatabaseCollection *m_collection;
>
>         AmarokProcess *m_scanner;
> +        QSharedMemory *m_scannerStateMemory; // a persistent storage of the current scanner state in case it needs to be restarted.
> +        QString       m_sharedMemoryKey;
>         XmlParseJob *m_parser;
>
>         int m_restartCount;
> diff --git a/utilities/collectionscanner/CollectionScanner.cpp b/utilities/collectionscanner/CollectionScanner.cpp
> index 7a4b588..e188272 100644
> --- a/utilities/collectionscanner/CollectionScanner.cpp
> +++ b/utilities/collectionscanner/CollectionScanner.cpp
> @@ -36,10 +36,13 @@
>  #include <QStringList>
>  #include <QDir>
>  #include <QFile>
> -#include <QSettings>
>  #include <QDateTime>
>  #include <QXmlStreamReader>
>  #include <QXmlStreamWriter>
> +#include <QSharedMemory>
> +#include <QByteArray>
> +#include <QTextStream>
> +#include <QDebug>
>
>  #include <audiblefiletyperesolver.h>
>  #include <realmediafiletyperesolver.h>
> @@ -54,6 +57,157 @@ main( int argc, char *argv[] )
>     return scanner.exec();
>  }
>
> +
> +// ------------ the scanning state -----------
> +
> +CollectionScanner::ScanningState::ScanningState()
> +        : m_sharedMemory(0)
> +{
> +}
> +
> +CollectionScanner::ScanningState::~ScanningState()
> +{
> +    delete m_sharedMemory;
> +}
> +
> +void
> +CollectionScanner::ScanningState::setKey( const QString &key )
> +{
> +    delete m_sharedMemory;
> +    m_sharedMemory = new QSharedMemory( key );
> +    if( m_sharedMemory->attach() )
> +        readFull();
> +}
> +
> +bool
> +CollectionScanner::ScanningState::isValid() const
> +{
> +    return m_sharedMemory && m_sharedMemory->isAttached();
> +}
> +
> +QString
> +CollectionScanner::ScanningState::lastDirectory() const
> +{ return m_lastDirectory; }
> +
> +void
> +CollectionScanner::ScanningState::setLastDirectory( const QString &dir )
> +{
> +    if( dir == m_lastDirectory )
> +        return;
> +
> +    m_lastDirectory = dir;
> +    writeFull();
> +}
> +
> +QStringList
> +CollectionScanner::ScanningState::directories() const
> +{ return m_directories; }
> +
> +void
> +CollectionScanner::ScanningState::setDirectories( const QStringList &directories )
> +{
> +    if( directories == m_directories )
> +        return;
> +
> +    m_directories = directories;
> +    writeFull();
> +}
> +
> +QStringList
> +CollectionScanner::ScanningState::badFiles() const
> +{ return m_badFiles; }
> +
> +void
> +CollectionScanner::ScanningState::setBadFiles( const QStringList &badFiles )
> +{
> +    if( badFiles == m_badFiles )
> +        return;
> +
> +    m_badFiles = badFiles;
> +    writeFull();
> +}
> +
> +QString
> +CollectionScanner::ScanningState::lastFile() const
> +{ return m_lastFile; }
> +
> +void
> +CollectionScanner::ScanningState::setLastFile( const QString &file )
> +{
> +    if( file == m_lastFile )
> +        return;
> +
> +    m_sharedMemory->lock();
> +    QByteArray data = QByteArray::fromRawData((char*)m_sharedMemory->data(), m_sharedMemory->size());
> +    QTextStream out( &data );
> +    out.seek( m_lastFilePos );
> +    out << m_lastFile;
> +    m_sharedMemory->unlock();
> +}
> +
> +void
> +CollectionScanner::ScanningState::readFull()
> +{
> +    if( !isValid() )
> +        return;
> +
> +    m_sharedMemory->lock();
> +    QByteArray data = QByteArray::fromRawData((char*)m_sharedMemory->data(), m_sharedMemory->size());
> +    QTextStream in(&data, QIODevice::ReadOnly);
> +
> +    int count;
> +
> +    in >> m_lastDirectory;
> +
> +    m_directories.clear();
> +    in >> count;
> +    for( int i=0; i<count; i++ )
> +    {
> +        QString s;
> +        in >> s;
> +        m_directories.append( s );
> +    }
> +
> +    m_badFiles.clear();
> +    in >> count;
> +    for( int i=0; i<count; i++ )
> +    {
> +        QString s;
> +        in >> s;
> +        m_badFiles.append( s );
> +    }
> +
> +    m_lastFilePos = in.pos();
> +    in >> m_lastFile;
> +
> +    m_sharedMemory->unlock();
> +}
> +
> +void
> +CollectionScanner::ScanningState::writeFull()
> +{
> +    if( !isValid() )
> +        return;
> +
> +    m_sharedMemory->lock();
> +    QByteArray data = QByteArray::fromRawData((char*)m_sharedMemory->data(), m_sharedMemory->size());
> +    QTextStream out( &data );
> +    out << m_lastDirectory;
> +    out << m_directories.count();
> +    foreach( const QString &s, m_directories )
> +        out << s;
> +    out << m_badFiles.count();
> +    foreach( const QString &s, m_badFiles )
> +        out << s;
> +    m_lastFilePos = out.pos();
> +    out << m_lastFile;
> +    qWarning("shared memory at %d of %d", out.pos(), m_sharedMemory->size());
> +    m_sharedMemory->unlock();
> +}
> +
> +
> +// ------------ the scanner -----------
> +
>  CollectionScanner::Scanner::Scanner( int &argc, char **argv )
>         : QCoreApplication( argc, argv )
>         , m_charset( false )
> @@ -78,19 +232,6 @@ CollectionScanner::Scanner::Scanner( int &argc, char **argv )
>     TagLib::FileRef::addFileTypeResolver(new ASFFileTypeResolver);
>     TagLib::FileRef::addFileTypeResolver(new MP4FileTypeResolver);
>     TagLib::FileRef::addFileTypeResolver(new WAVFileTypeResolver);
> -
> -    QString env = qgetenv( "KDEHOME" );
> -    if( env.isEmpty() || !QDir(env).isReadable() )
> -        env = QString( "%1/.kde" ).arg( QDir::homePath() );
> -    QString path = env + QLatin1String("/share/apps");
> -
> -    // give two different settings.
> -    // prevent the possibility that an incremental and a non-incremental scan clash
> -    QSettings::setPath( QSettings::NativeFormat, QSettings::UserScope, path );
> -    if( m_incremental )
> -        m_settings = new QSettings( "amarok", "CollectionScannerIncremental", this );
> -    else
> -        m_settings = new QSettings( "amarok", "CollectionScanner", this );
>  }
>
>
> @@ -144,11 +285,11 @@ CollectionScanner::Scanner::doJob() //SLOT
>     // --- determine the directories we have to scan
>     QStringList entries;
>
> -    // -- when restarting read them from the settings
> -    if( m_restart )
> +    // -- when restarting read them from the shared memory
> +    if( m_restart && m_scanningState.isValid() )
>     {
> -        QString lastEntry = m_settings->value( "lastDirectory" ).toString();
> -        entries = m_settings->value( "directories" ).toStringList();
> +        QString lastEntry = m_scanningState.lastDirectory();
> +        entries = m_scanningState.directories();
>
>         // remove the entries we already scanned
>         while( entries.front() != lastEntry && !entries.empty() )
> @@ -180,8 +321,8 @@ CollectionScanner::Scanner::doJob() //SLOT
>         }
>
>         entries = entriesSet.toList();
> -        m_settings->setValue( "lastDirectory", QString() );
> -        m_settings->setValue( "directories", entries );
> +        m_scanningState.setLastDirectory( QString() );
> +        m_scanningState.setDirectories( entries );
>     }
>
>     if( !m_restart )
> @@ -194,7 +335,7 @@ CollectionScanner::Scanner::doJob() //SLOT
>     // --- now do the scanning
>     foreach( QString path, entries )
>     {
> -        CollectionScanner::Directory dir( path, m_settings,
> +        CollectionScanner::Directory dir( path, &m_scanningState,
>                                           m_incremental && !isModified( path ) );
>
>         xmlWriter.writeStartElement( "directory" );
> @@ -295,6 +436,14 @@ CollectionScanner::Scanner::readArgs()
>                     missingArg = true;
>                 argnum++;
>             }
> +            else if( myarg == "sharedmemory" )
> +            {
> +                if( argslist.count() > argnum + 1 )
> +                    m_scanningState.setKey( argslist.at( argnum + 1 ) );
> +                else
> +                    missingArg = true;
> +                argnum++;
> +            }
>             else if( myarg == "version" )
>                 displayVersion();
>             else if( myarg == "incremental" )
> @@ -392,6 +541,7 @@ CollectionScanner::Scanner::displayHelp( const QString &error )
>         "-i, --incremental       : Incremental scan (modified folders only)\n"
>         "-s, --restart           : After a crash, restart the scanner in its last position\n"
>         "    --idlepriority      : Run at idle priority\n"
> +        "    --sharedmemory <key> : A shared memory segment to be used for restarting a scan\n"
>         "    --newer <path>      : Only scan directories if modification time is new than <path>\n"
>         "                          Only useful in incremental scan mode\n"
>         "    --batch <path>      : Add the directories from the batch xml file\n"
> diff --git a/utilities/collectionscanner/CollectionScanner.h b/utilities/collectionscanner/CollectionScanner.h
> index 4a21248..4968c98 100644
> --- a/utilities/collectionscanner/CollectionScanner.h
> +++ b/utilities/collectionscanner/CollectionScanner.h
> @@ -33,11 +33,49 @@
>  #include <QSet>
>  #include <QStringList>
>
> -class QSettings;
> +class QSharedMemory;
>
>  namespace CollectionScanner
>  {
>
> +/** A class used to store the current scanning state in a shared memory segment.
> +    Storing the state of the scanner shouldn't cause a file access.
> +    We are using a shared memory that the amarok process holds open until the scanning
> +    is finished to store the state.
> + */
> +class ScanningState
> +{
> +    public:
> +        ScanningState();
> +        ~ScanningState();
> +
> +        void setKey( const QString &key );
> +        bool isValid() const;
> +
> +        QString lastDirectory() const;
> +        void setLastDirectory( const QString &dir );
> +        QStringList directories() const;
> +        void setDirectories( const QStringList &directories );
> +
> +        QStringList badFiles() const;
> +        void setBadFiles( const QStringList &badFiles );
> +        QString lastFile() const;
> +        void setLastFile( const QString &file );
> +
> +    private:
> +        void readFull();
> +        void writeFull();
> +
> +        QSharedMemory *m_sharedMemory;
> +
> +        QString m_lastDirectory;
> +        QStringList m_directories;
> +        QStringList m_badFiles;
> +        QString m_lastFile;
> +        qint64 m_lastFilePos;
> +};
> +
> +
>  /**
>  * @class Scanner
>  * @short Scans directories and builds the Collection
> @@ -92,8 +130,7 @@ private:
>     bool                  m_idlePriority;
>
>     QString               m_mtimeFile;
> -
> -    QSettings*            m_settings;
> +    ScanningState         m_scanningState;
>
>
>     // Disable copy constructor and assignment
> diff --git a/utilities/collectionscanner/Directory.cpp b/utilities/collectionscanner/Directory.cpp
> index 76ceb99..7acd8ed 100644
> --- a/utilities/collectionscanner/Directory.cpp
> +++ b/utilities/collectionscanner/Directory.cpp
> @@ -37,7 +37,9 @@
>
>  #ifdef UTILITIES_BUILD
>
> -CollectionScanner::Directory::Directory( const QString &path, QSettings *settings, bool skip )
> +CollectionScanner::Directory::Directory( const QString &path,
> +                                         CollectionScanner::ScanningState *state,
> +                                         bool skip )
>     : m_ignored( false )
>  {
>     m_path = path;
> @@ -55,26 +57,26 @@ CollectionScanner::Directory::Directory( const QString &path, QSettings *setting
>         return;
>     }
>
> -
> -    QStringList validImages;    validImages    << "jpg" << "png" << "gif" << "jpeg" << "bmp";
> -    QStringList validPlaylists; validPlaylists << "m3u" << "pls" << "xspf";
> +    QStringList validImages;
> +    validImages << "jpg" << "png" << "gif" << "jpeg" << "bmp" << "svg" << "xpm";
> +    QStringList validPlaylists;
> +    validPlaylists << "m3u" << "pls" << "xspf";
>
>     // --- check if we were restarted and failed at a file
>     QStringList badFiles;
>
> -    QString lastEntry = settings->value( "lastDirectory" ).toString();
> -    if( lastEntry == path )
> +    if( state->lastDirectory() == path )
>     {
> -        badFiles << settings->value( "lastFile" ).toString();
> -        badFiles << settings->value( "badFiles" ).toStringList();
> +        badFiles << state->badFiles();
> +        badFiles << state->lastFile();
>
> -        settings->setValue( "badFiles", badFiles );
> +        state->setBadFiles( badFiles );
>     }
>     else
>     {
> -        settings->setValue( "lastDirectory", path );
> -        settings->setValue( "lastFile", QString() );
> -        settings->setValue( "badFiles", QStringList() );
> +        state->setLastDirectory( path );
> +        state->setLastFile( QString() );
> +        state->setBadFiles( badFiles );
>     }
>
>     QStringList covers;
> @@ -92,10 +94,6 @@ CollectionScanner::Directory::Directory( const QString &path, QSettings *setting
>         if( badFiles.contains( f.absoluteFilePath() ) )
>             continue;
>
> -        // remember the last file before it get's dangerous. Before starting taglib
> -        settings->setValue( "lastFile", f.absoluteFilePath() );
> -        settings->sync();
> -
>         const QString suffix  = fi.suffix().toLower();
>         const QString filePath = f.absoluteFilePath();
>
> @@ -103,8 +101,8 @@ CollectionScanner::Directory::Directory( const QString &path, QSettings *setting
>         if( validImages.contains( suffix ) )
>         {
>             covers += filePath;
> -
>         }
> +
>         // -- playlist ?
>         else if( validPlaylists.contains( suffix ) )
>         {
> @@ -114,6 +112,9 @@ CollectionScanner::Directory::Directory( const QString &path, QSettings *setting
>         // -- audio track ?
>         else
>         {
> +            // remember the last file before it get's dangerous. Before starting taglib
> +            state->setLastFile( f.absoluteFilePath() );
> +
>             CollectionScanner::Track newTrack( filePath );
>             if( newTrack.isValid() )
>             {
> diff --git a/utilities/collectionscanner/Directory.h b/utilities/collectionscanner/Directory.h
> index 8ba28f7..091390f 100644
> --- a/utilities/collectionscanner/Directory.h
> +++ b/utilities/collectionscanner/Directory.h
> @@ -34,16 +34,17 @@ class QXmlStreamWriter;
>  namespace CollectionScanner
>  {
>
> +class ScanningState;
> +
>  /**
>  * @class Directory
>  * @short Represents a scanned directory and it's contents
>  */
> -
>  class Directory
>  {
>  public:
>  #ifdef UTILITIES_BUILD
> -    Directory( const QString &path, QSettings *settings, bool skip = false );
> +    Directory( const QString &path, ScanningState *state, bool skip );
>  #endif // UTILITIES_BUILD
>
>     /** Reads a directory from an xml stream.
> diff --git a/utilities/collectionscanner/Track.cpp b/utilities/collectionscanner/Track.cpp
> index 9291662..d0458d7 100644
> --- a/utilities/collectionscanner/Track.cpp
> +++ b/utilities/collectionscanner/Track.cpp
> @@ -129,7 +129,7 @@ CollectionScanner::Track::Track( const QString &path)
>     if( !fileref.isNull() )
>     {
>         tag = fileref.tag();
> -        if ( tag )
> +        if( tag )
>         {
>             m_title = strip( tag->title() );
>             m_artist = strip( tag->artist() );
> @@ -404,7 +404,7 @@ CollectionScanner::Track::Track( const QString &path)
>
>     m_filesize = QFile( path ).size();
>
> -    if( m_uniqueid.isEmpty() )
> +    if( m_valid && m_uniqueid.isEmpty() )
>     {
>         AFTUtility aftutil;
>         m_uniqueid = "amarok-sqltrackuid://" + aftutil.readUniqueId( path );
>


More information about the Amarok-devel mailing list