Try to fix intermittent deadlocks with encoding servers.
[dcpomatic.git] / src / lib / encoder.cc
index 36e4fe55d4adba72ca2aff7416393c6a5e7bf85c..652412ddf01d455941c8760db1b080cf2cef68eb 100644 (file)
@@ -53,14 +53,11 @@ using boost::weak_ptr;
 using boost::optional;
 using dcp::Data;
 
-int const Encoder::_history_size = 25;
+int const Encoder::_history_size = 200;
 
 /** @param f Film that we are encoding */
 Encoder::Encoder (shared_ptr<const Film> film, shared_ptr<Writer> writer)
        : _film (film)
-       , _position (0)
-       , _terminate_enqueue (false)
-       , _terminate_encoding (false)
        , _writer (writer)
 {
        servers_list_changed ();
@@ -68,19 +65,37 @@ Encoder::Encoder (shared_ptr<const Film> film, shared_ptr<Writer> writer)
 
 Encoder::~Encoder ()
 {
-       terminate_threads ();
-
-       boost::mutex::scoped_lock lm (_queue_mutex);
-       _terminate_enqueue = true;
-       _full_condition.notify_all ();
-       _empty_condition.notify_all ();
+       try {
+               terminate_threads ();
+       } catch (...) {
+               /* Destructors must not throw exceptions; anything bad
+                  happening now is too late to worry about anyway,
+                  I think.
+               */
+       }
 }
 
 void
 Encoder::begin ()
 {
        if (!EncodeServerFinder::instance()->disabled ()) {
-               _server_found_connection = EncodeServerFinder::instance()->ServersListChanged.connect (boost::bind (&Encoder::servers_list_changed, this));
+               weak_ptr<Encoder> wp = shared_from_this ();
+               _server_found_connection = EncodeServerFinder::instance()->ServersListChanged.connect (
+                       boost::bind (&Encoder::call_servers_list_changed, wp)
+                       );
+       }
+}
+
+/* We don't want the servers-list-changed callback trying to do things
+   during destruction of Encoder, and I think this is the neatest way
+   to achieve that.
+*/
+void
+Encoder::call_servers_list_changed (weak_ptr<Encoder> encoder)
+{
+       shared_ptr<Encoder> e = encoder.lock ();
+       if (e) {
+               e->servers_list_changed ();
        }
 }
 
@@ -147,12 +162,15 @@ Encoder::current_encoding_rate () const
        return _history_size / (seconds (now) - seconds (_time_history.back ()));
 }
 
-/** @return Number of video frames that have been sent out */
+/** @return Number of video frames that have been queued for encoding */
 int
-Encoder::video_frames_out () const
+Encoder::video_frames_enqueued () const
 {
-       boost::mutex::scoped_lock (_state_mutex);
-       return _position;
+       if (!_last_player_video) {
+               return 0;
+       }
+
+       return _last_player_video->time().frames_floor (_film->video_frame_rate ());
 }
 
 /** Should be called when a frame has been encoded successfully.
@@ -177,16 +195,7 @@ Encoder::frame_done ()
  *  for this DCP frame.
  */
 void
-Encoder::encode (list<shared_ptr<PlayerVideo> > pv)
-{
-       BOOST_FOREACH (shared_ptr<PlayerVideo> i, pv) {
-               enqueue (i);
-       }
-       ++_position;
-}
-
-void
-Encoder::enqueue (shared_ptr<PlayerVideo> pv)
+Encoder::encode (shared_ptr<PlayerVideo> pv)
 {
        _waker.nudge ();
 
@@ -198,17 +207,13 @@ Encoder::enqueue (shared_ptr<PlayerVideo> pv)
 
        boost::mutex::scoped_lock queue_lock (_queue_mutex);
 
-       /* XXX: discard 3D here if required */
-
-       /* Wait until the queue has gone down a bit */
-       while (_queue.size() >= threads * 2 && !_terminate_enqueue) {
-               LOG_TIMING ("decoder-sleep queue=%1", _queue.size());
+       /* Wait until the queue has gone down a bit.  Allow one thing in the queue even
+          when there are no threads.
+       */
+       while (_queue.size() >= (threads * 2) + 1) {
+               LOG_TIMING ("decoder-sleep queue=%1 threads=%2", _queue.size(), threads);
                _full_condition.wait (queue_lock);
-               LOG_TIMING ("decoder-wake queue=%1", _queue.size());
-       }
-
-       if (_terminate_enqueue) {
-               return;
+               LOG_TIMING ("decoder-wake queue=%1 threads=%2", _queue.size(), threads);
        }
 
        _writer->rethrow ();
@@ -218,22 +223,24 @@ Encoder::enqueue (shared_ptr<PlayerVideo> pv)
        */
        rethrow ();
 
-       if (_writer->can_fake_write (_position)) {
+       Frame const position = pv->time().frames_floor(_film->video_frame_rate());
+
+       if (_writer->can_fake_write (position)) {
                /* We can fake-write this frame */
-               _writer->fake_write (_position, pv->eyes ());
+               _writer->fake_write (position, pv->eyes ());
                frame_done ();
        } else if (pv->has_j2k ()) {
                /* This frame already has JPEG2000 data, so just write it */
-               _writer->write (pv->j2k(), _position, pv->eyes ());
-       } else if (_last_player_video && _writer->can_repeat(_position) && pv->same (_last_player_video)) {
-               _writer->repeat (_position, pv->eyes ());
+               _writer->write (pv->j2k(), position, pv->eyes ());
+       } else if (_last_player_video && _writer->can_repeat(position) && pv->same (_last_player_video)) {
+               _writer->repeat (position, pv->eyes ());
        } else {
                /* Queue this new frame for encoding */
                LOG_TIMING ("add-frame-to-queue queue=%1", _queue.size ());
                _queue.push_back (shared_ptr<DCPVideo> (
                                          new DCPVideo (
                                                  pv,
-                                                 _position,
+                                                 position,
                                                  _film->video_frame_rate(),
                                                  _film->j2k_bandwidth(),
                                                  _film->resolution(),
@@ -253,19 +260,17 @@ Encoder::enqueue (shared_ptr<PlayerVideo> pv)
 void
 Encoder::terminate_threads ()
 {
-       {
-               boost::mutex::scoped_lock queue_lock (_queue_mutex);
-               _terminate_encoding = true;
-       }
-
        boost::mutex::scoped_lock threads_lock (_threads_mutex);
 
        int n = 0;
        for (list<boost::thread *>::iterator i = _threads.begin(); i != _threads.end(); ++i) {
                LOG_GENERAL ("Terminating thread %1 of %2", n + 1, _threads.size ());
                (*i)->interrupt ();
-               if ((*i)->joinable ()) {
+               DCPOMATIC_ASSERT ((*i)->joinable ());
+               try {
                        (*i)->join ();
+               } catch (boost::thread_interrupted& e) {
+                       /* This is to be expected */
                }
                delete *i;
                LOG_GENERAL_NC ("Thread terminated");
@@ -273,7 +278,6 @@ Encoder::terminate_threads ()
        }
 
        _threads.clear ();
-       _terminate_encoding = false;
 }
 
 void
@@ -296,14 +300,10 @@ try
 
                LOG_TIMING ("encoder-sleep thread=%1", boost::this_thread::get_id());
                boost::mutex::scoped_lock lock (_queue_mutex);
-               while (_queue.empty () && !_terminate_encoding) {
+               while (_queue.empty ()) {
                        _empty_condition.wait (lock);
                }
 
-               if (_terminate_encoding) {
-                       return;
-               }
-
                LOG_TIMING ("encoder-wake thread=%1 queue=%2", boost::this_thread::get_id(), _queue.size());
                shared_ptr<DCPVideo> vf = _queue.front ();
                LOG_TIMING ("encoder-pop thread=%1 frame=%2 eyes=%3", boost::this_thread::get_id(), vf->index(), vf->eyes ());
@@ -366,6 +366,10 @@ try
                _full_condition.notify_all ();
        }
 }
+catch (boost::thread_interrupted& e) {
+       /* Ignore these and just stop the thread */
+       _full_condition.notify_all ();
+}
 catch (...)
 {
        store_current ();