Fix deadlock and potential race condition when editing MIDI.

git-svn-id: svn://localhost/ardour2/branches/3.0@4614 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
David Robillard 2009-02-17 06:09:37 +00:00
parent 3f24977735
commit f219a53744
6 changed files with 68 additions and 56 deletions

View file

@ -85,6 +85,11 @@ class MidiSource : virtual public Source
virtual void load_model(bool lock=true, bool force_reload=false) = 0; virtual void load_model(bool lock=true, bool force_reload=false) = 0;
virtual void destroy_model() = 0; virtual void destroy_model() = 0;
/** This must be called with the source lock held whenever the
* source/model contents have been changed (reset iterators/cache/etc).
*/
void invalidate();
void set_note_mode(NoteMode mode); void set_note_mode(NoteMode mode);
void set_timeline_position (int64_t pos); void set_timeline_position (int64_t pos);

View file

@ -115,8 +115,9 @@ class Source : public SessionObject, public ARDOUR::Readable, public boost::nonc
virtual const Evoral::TimeConverter<double, nframes_t>& time_converter() const { virtual const Evoral::TimeConverter<double, nframes_t>& time_converter() const {
return Evoral::IdentityConverter<double, nframes_t>(); return Evoral::IdentityConverter<double, nframes_t>();
} }
Flag flags() const { return _flags; } Glib::Mutex& mutex() { return _lock; }
Flag flags() const { return _flags; }
protected: protected:
DataType _type; DataType _type;

View file

@ -1,22 +1,22 @@
/* /*
Copyright (C) 2007 Paul Davis Copyright (C) 2007 Paul Davis
Written by Dave Robillard, 2007 Author: Dave Robillard
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or the Free Software Foundation; either version 2 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/ */
#define __STDC_LIMIT_MACROS 1 #define __STDC_LIMIT_MACROS 1
@ -37,11 +37,10 @@ using namespace std;
using namespace ARDOUR; using namespace ARDOUR;
using namespace PBD; using namespace PBD;
MidiModel::MidiModel(MidiSource *s, size_t size) MidiModel::MidiModel(MidiSource* s, size_t size)
: AutomatableSequence<TimeType>(s->session(), size) : AutomatableSequence<TimeType>(s->session(), size)
, _midi_source(s) , _midi_source(s)
{ {
//cerr << "MidiModel \"" << s->name() << "\" constructed: " << this << endl;
} }
/** Start a new command. /** Start a new command.
@ -50,7 +49,8 @@ MidiModel::MidiModel(MidiSource *s, size_t size)
* can be held on to for as long as the caller wishes, or discarded without * can be held on to for as long as the caller wishes, or discarded without
* formality, until apply_command is called and ownership is taken. * formality, until apply_command is called and ownership is taken.
*/ */
MidiModel::DeltaCommand* MidiModel::new_delta_command(const string name) MidiModel::DeltaCommand*
MidiModel::new_delta_command(const string name)
{ {
DeltaCommand* cmd = new DeltaCommand(_midi_source->model(), name); DeltaCommand* cmd = new DeltaCommand(_midi_source->model(), name);
return cmd; return cmd;
@ -73,16 +73,14 @@ MidiModel::apply_command(Session& session, Command* cmd)
// DeltaCommand // DeltaCommand
MidiModel::DeltaCommand::DeltaCommand(boost::shared_ptr<MidiModel> m, MidiModel::DeltaCommand::DeltaCommand(boost::shared_ptr<MidiModel> m, const std::string& name)
const std::string& name)
: Command(name) : Command(name)
, _model(m) , _model(m)
, _name(name) , _name(name)
{ {
} }
MidiModel::DeltaCommand::DeltaCommand(boost::shared_ptr<MidiModel> m, MidiModel::DeltaCommand::DeltaCommand(boost::shared_ptr<MidiModel> m, const XMLNode& node)
const XMLNode& node)
: _model(m) : _model(m)
{ {
set_state(node); set_state(node);
@ -91,7 +89,6 @@ MidiModel::DeltaCommand::DeltaCommand(boost::shared_ptr<MidiModel> m,
void void
MidiModel::DeltaCommand::add(const boost::shared_ptr< Evoral::Note<TimeType> > note) MidiModel::DeltaCommand::add(const boost::shared_ptr< Evoral::Note<TimeType> > note)
{ {
//cerr << "MEC: apply" << endl;
_removed_notes.remove(note); _removed_notes.remove(note);
_added_notes.push_back(note); _added_notes.push_back(note);
} }
@ -99,7 +96,6 @@ MidiModel::DeltaCommand::add(const boost::shared_ptr< Evoral::Note<TimeType> > n
void void
MidiModel::DeltaCommand::remove(const boost::shared_ptr< Evoral::Note<TimeType> > note) MidiModel::DeltaCommand::remove(const boost::shared_ptr< Evoral::Note<TimeType> > note)
{ {
//cerr << "MEC: remove" << endl;
_added_notes.remove(note); _added_notes.remove(note);
_removed_notes.push_back(note); _removed_notes.push_back(note);
} }
@ -110,12 +106,10 @@ MidiModel::DeltaCommand::operator()()
// This could be made much faster by using a priority_queue for added and // This could be made much faster by using a priority_queue for added and
// removed notes (or sort here), and doing a single iteration over _model // removed notes (or sort here), and doing a single iteration over _model
Glib::Mutex::Lock lm (_model->_midi_source->mutex());
_model->_midi_source->invalidate(); // release model read lock
_model->write_lock(); _model->write_lock();
// Store the current seek position so we can restore the read iterator
// after modifying the contents of the model
const TimeType read_time = _model->read_time();
for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i)
_model->add_note_unlocked(*i); _model->add_note_unlocked(*i);
@ -123,9 +117,6 @@ MidiModel::DeltaCommand::operator()()
_model->remove_note_unlocked(*i); _model->remove_note_unlocked(*i);
_model->write_unlock(); _model->write_unlock();
// FIXME: race?
_model->read_seek(read_time); // restore read position
_model->ContentsChanged(); /* EMIT SIGNAL */ _model->ContentsChanged(); /* EMIT SIGNAL */
} }
@ -135,12 +126,10 @@ MidiModel::DeltaCommand::undo()
// This could be made much faster by using a priority_queue for added and // This could be made much faster by using a priority_queue for added and
// removed notes (or sort here), and doing a single iteration over _model // removed notes (or sort here), and doing a single iteration over _model
Glib::Mutex::Lock lm (_model->_midi_source->mutex());
_model->_midi_source->invalidate(); // release model read lock
_model->write_lock(); _model->write_lock();
// Store the current seek position so we can restore the read iterator
// after modifying the contents of the model
const TimeType read_time = _model->read_time();
for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) for (NoteList::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i)
_model->remove_note_unlocked(*i); _model->remove_note_unlocked(*i);
@ -148,16 +137,13 @@ MidiModel::DeltaCommand::undo()
_model->add_note_unlocked(*i); _model->add_note_unlocked(*i);
_model->write_unlock(); _model->write_unlock();
// FIXME: race?
_model->read_seek(read_time); // restore read position
_model->ContentsChanged(); /* EMIT SIGNAL */ _model->ContentsChanged(); /* EMIT SIGNAL */
} }
XMLNode& XMLNode&
MidiModel::DeltaCommand::marshal_note(const boost::shared_ptr< Evoral::Note<TimeType> > note) MidiModel::DeltaCommand::marshal_note(const boost::shared_ptr< Evoral::Note<TimeType> > note)
{ {
XMLNode *xml_note = new XMLNode("note"); XMLNode* xml_note = new XMLNode("note");
ostringstream note_str(ios::ate); ostringstream note_str(ios::ate);
note_str << int(note->note()); note_str << int(note->note());
xml_note->add_property("note", note_str.str()); xml_note->add_property("note", note_str.str());
@ -248,13 +234,13 @@ MidiModel::DeltaCommand::set_state(const XMLNode& delta_command)
} }
_added_notes.clear(); _added_notes.clear();
XMLNode *added_notes = delta_command.child(ADDED_NOTES_ELEMENT); XMLNode* added_notes = delta_command.child(ADDED_NOTES_ELEMENT);
XMLNodeList notes = added_notes->children(); XMLNodeList notes = added_notes->children();
transform(notes.begin(), notes.end(), back_inserter(_added_notes), transform(notes.begin(), notes.end(), back_inserter(_added_notes),
sigc::mem_fun(*this, &DeltaCommand::unmarshal_note)); sigc::mem_fun(*this, &DeltaCommand::unmarshal_note));
_removed_notes.clear(); _removed_notes.clear();
XMLNode *removed_notes = delta_command.child(REMOVED_NOTES_ELEMENT); XMLNode* removed_notes = delta_command.child(REMOVED_NOTES_ELEMENT);
notes = removed_notes->children(); notes = removed_notes->children();
transform(notes.begin(), notes.end(), back_inserter(_removed_notes), transform(notes.begin(), notes.end(), back_inserter(_removed_notes),
sigc::mem_fun(*this, &DeltaCommand::unmarshal_note)); sigc::mem_fun(*this, &DeltaCommand::unmarshal_note));
@ -265,15 +251,15 @@ MidiModel::DeltaCommand::set_state(const XMLNode& delta_command)
XMLNode& XMLNode&
MidiModel::DeltaCommand::get_state() MidiModel::DeltaCommand::get_state()
{ {
XMLNode *delta_command = new XMLNode(DELTA_COMMAND_ELEMENT); XMLNode* delta_command = new XMLNode(DELTA_COMMAND_ELEMENT);
delta_command->add_property("midi-source", _model->midi_source()->id().to_s()); delta_command->add_property("midi-source", _model->midi_source()->id().to_s());
XMLNode *added_notes = delta_command->add_child(ADDED_NOTES_ELEMENT); XMLNode* added_notes = delta_command->add_child(ADDED_NOTES_ELEMENT);
for_each(_added_notes.begin(), _added_notes.end(), sigc::compose( for_each(_added_notes.begin(), _added_notes.end(), sigc::compose(
sigc::mem_fun(*added_notes, &XMLNode::add_child_nocopy), sigc::mem_fun(*added_notes, &XMLNode::add_child_nocopy),
sigc::mem_fun(*this, &DeltaCommand::marshal_note))); sigc::mem_fun(*this, &DeltaCommand::marshal_note)));
XMLNode *removed_notes = delta_command->add_child(REMOVED_NOTES_ELEMENT); XMLNode* removed_notes = delta_command->add_child(REMOVED_NOTES_ELEMENT);
for_each(_removed_notes.begin(), _removed_notes.end(), sigc::compose( for_each(_removed_notes.begin(), _removed_notes.end(), sigc::compose(
sigc::mem_fun(*removed_notes, &XMLNode::add_child_nocopy), sigc::mem_fun(*removed_notes, &XMLNode::add_child_nocopy),
sigc::mem_fun(*this, &DeltaCommand::marshal_note))); sigc::mem_fun(*this, &DeltaCommand::marshal_note)));

View file

@ -103,6 +103,12 @@ MidiSource::set_state (const XMLNode& node)
return 0; return 0;
} }
void
MidiSource::invalidate ()
{
_model_iter.invalidate();
}
nframes_t nframes_t
MidiSource::midi_read (MidiRingBuffer<nframes_t>& dst, nframes_t start, nframes_t cnt, MidiSource::midi_read (MidiRingBuffer<nframes_t>& dst, nframes_t start, nframes_t cnt,
nframes_t stamp_offset, nframes_t negative_stamp_offset) const nframes_t stamp_offset, nframes_t negative_stamp_offset) const
@ -114,7 +120,7 @@ MidiSource::midi_read (MidiRingBuffer<nframes_t>& dst, nframes_t start, nframes_
Evoral::Sequence<double>::const_iterator& i = _model_iter; Evoral::Sequence<double>::const_iterator& i = _model_iter;
if (_last_read_end == 0 || start != _last_read_end) { if (_last_read_end == 0 || start != _last_read_end || !i.valid()) {
cerr << "MidiSource::midi_read seeking to frame " << start << endl; cerr << "MidiSource::midi_read seeking to frame " << start << endl;
for (i = _model->begin(); i != _model->end(); ++i) { for (i = _model->begin(); i != _model->end(); ++i) {
if (BEATS_TO_FRAMES(i->time()) >= start) { if (BEATS_TO_FRAMES(i->time()) >= start) {

View file

@ -63,8 +63,6 @@ class Sequence : virtual public ControlSet {
public: public:
Sequence(const TypeMap& type_map, size_t size=0); Sequence(const TypeMap& type_map, size_t size=0);
bool read_locked() { return _read_iter.locked(); }
void write_lock(); void write_lock();
void write_unlock(); void write_unlock();
@ -126,6 +124,8 @@ public:
inline bool valid() const { return !_is_end && _event; } inline bool valid() const { return !_is_end && _event; }
inline bool locked() const { return _locked; } inline bool locked() const { return _locked; }
void invalidate();
const Event<Time>& operator*() const { return *_event; } const Event<Time>& operator*() const { return *_event; }
const boost::shared_ptr< Event<Time> > operator->() const { return _event; } const boost::shared_ptr< Event<Time> > operator->() const { return _event; }
@ -159,9 +159,6 @@ public:
const_iterator begin(Time t=0) const { return const_iterator(*this, t); } const_iterator begin(Time t=0) const { return const_iterator(*this, t); }
const const_iterator& end() const { return _end_iter; } const const_iterator& end() const { return _end_iter; }
void read_seek(Time t) { _read_iter = begin(t); }
Time read_time() const { return _read_iter.valid() ? _read_iter->time() : 0.0; }
bool control_to_midi_event(boost::shared_ptr< Event<Time> >& ev, bool control_to_midi_event(boost::shared_ptr< Event<Time> >& ev,
const ControlIterator& iter) const; const ControlIterator& iter) const;
@ -175,8 +172,7 @@ public:
uint8_t highest_note() const { return _highest_note; } uint8_t highest_note() const { return _highest_note; }
protected: protected:
mutable const_iterator _read_iter; bool _edited;
bool _edited;
private: private:
friend class const_iterator; friend class const_iterator;

View file

@ -224,6 +224,26 @@ Sequence<Time>::const_iterator::~const_iterator()
} }
} }
template<typename Time>
void
Sequence<Time>::const_iterator::invalidate()
{
while (!_active_notes.empty()) {
_active_notes.pop();
}
_type = NIL;
_is_end = true;
if (_seq) {
_note_iter = _seq->notes().end();
_sysex_iter = _seq->sysexes().end();
}
_control_iter = _control_iters.end();
if (_locked) {
_seq->read_unlock();
_locked = false;
}
}
template<typename Time> template<typename Time>
const typename Sequence<Time>::const_iterator& const typename Sequence<Time>::const_iterator&
Sequence<Time>::const_iterator::operator++() Sequence<Time>::const_iterator::operator++()
@ -407,8 +427,7 @@ Sequence<Time>::const_iterator::operator=(const const_iterator& other)
template<typename Time> template<typename Time>
Sequence<Time>::Sequence(const TypeMap& type_map, size_t size) Sequence<Time>::Sequence(const TypeMap& type_map, size_t size)
: _read_iter(*this, DBL_MAX) : _edited(false)
, _edited(false)
, _type_map(type_map) , _type_map(type_map)
, _notes(size) , _notes(size)
, _writing(false) , _writing(false)
@ -508,7 +527,6 @@ Sequence<Time>::clear()
_notes.clear(); _notes.clear();
for (Controls::iterator li = _controls.begin(); li != _controls.end(); ++li) for (Controls::iterator li = _controls.begin(); li != _controls.end(); ++li)
li->second->list()->clear(); li->second->list()->clear();
_read_iter = end();
_lock.writer_unlock(); _lock.writer_unlock();
} }