Skip to content

Conversation

@carllin
Copy link

@carllin carllin commented Oct 24, 2018

Add CloseHandle to Json Http server to allow a different thread to close() after calling wait() on main server thread. Based on similar work for websockets: https://github.com/paritytech/jsonrpc/pull/151/files

Copy link
Member

@bkchr bkchr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good so far. Some minor nitpicks :)

http/src/lib.rs Outdated

/// A handle that allows closing of a server even if it owned by a thread blocked in `wait`.
pub struct CloseHandle {
close: Arc<Mutex<Option<Vec<oneshot::Sender<()>>>>>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More whitespaces.

}

/// A handle that allows closing of a server even if it owned by a thread blocked in `wait`.
pub struct CloseHandle {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should implement/derive Clone for the CloseHandle.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

futures::Sender, doesn't implement Clone(), so how should we approach this?

Copy link
Member

@bkchr bkchr Oct 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean the executor_close can not be cloned?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@carllin it's wrapped in Arc<Mutex anyway, so it doesn't really matter. If derive doesn't work then you need to implement Clone manually (and just clone the Arc)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bkchr, @tomusdrw, yes the executor_close cannot be cloned.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So let's wrap it into Arc as well. I also don't quite understand why do we need to close the executor when we close the server? It wasn't the case for WS or IPC server?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tomusdrw, in the current WS implementation, close() also closes the executor: https://github.com/paritytech/jsonrpc/blob/master/ws/src/server.rs#L149

Copy link
Contributor

@tomusdrw tomusdrw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, please add missing docs and re-format the code using tabs (see .editorconfig file).
Could you also explain why do we need close_handle for Executor and RpcEventLoop?

@carllin
Copy link
Author

carllin commented Oct 24, 2018

@tomusdrw I added the close_handle in Executor and RpcEventLoop because I wanted to support the following behavior:

  1. Grab the CloseHandle for the Server
  2. Call Wait() on the Server
  3. Call CloseHandle.Close()

Unfortunately, if the CloseHandle just held a reference to a lock to the Executors (as was done in the websockets implementation), b/c the Wait() function also simultaneously needs access to the Executors, this won't work. Thus the close_handle() on the reactor and executor return the inner Complete() signaler that executor->close() would have called anyway, so now the CloseHandle no longer needs shared access/lock to the Executor.

@carllin
Copy link
Author

carllin commented Oct 30, 2018

@tomusdrw, @bkchr, pinging for updates.


/// Returns a handle to the server that can be used to close it while another thread is
/// blocking in `wait`.
pub fn close_handle(&mut self) -> CloseHandle {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function shouldn't mutate anything, so it should be &self.

Suggested change
pub fn close_handle(&mut self) -> CloseHandle {
pub fn close_handle(&self) -> CloseHandle {
let executor_close: Option<Vec<_>> = self.executor.as_ref().map(|executors| {
executors
.iter()
.map(|executor| executor.close_handle())
.collect()
});
...
}

}

/// Returns a handle to close the underlying event loop
pub fn close_handle(&mut self) -> Option<futures::Complete<()>> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mut not needed

Suggested change
pub fn close_handle(&mut self) -> Option<futures::Complete<()>> {
pub fn close_handle(&self) -> Option<futures::Complete<()>> {


/// Returns the close signal that can be used to close the event loop even while
/// another thread is blocked on the event loop in "wait"
pub fn close_handle(&mut self) -> Option<futures::Complete<()>>{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function doesn't make much sense - the whole point of handle is to be able to have multiple instance of it.
This function here will take() the close handle and only return it for the first caller. Subsequent calls will always yield None.
What's more it violates the assumption in the rest of this struct (see expect("..") calls) - so every other function like close() or wait() will actually panic.

I still believe that having this function is completely unnecessary. See how it's done in WebSockets server - we own Executor via Arc<Mutex<Option<Executor>>> and close it completely inside CloseHandle (actually it's not perfect there, cause cloning the handle and calling close() on a clone would panic, but it's a separate issue).

/// jsonrpc http server instance
pub struct Server {
address: SocketAddr,
executor: Option<Vec<Executor>>,
Copy link
Contributor

@tomusdrw tomusdrw Oct 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

executor should most likely be wrapped in Arc<Mutex< as wel, so that it can be passed to CloseHandle. Actually it would be simpler to store CloseHandle directly in Server and just clone it when requested. CloseHandle should handle the case if Options are None.
To simplify it further you can just have:

pub struct Server {
   address: SocketAddr,
  close_handle: CloseHandle,
}

impl Server {
  ...
  pub fn close_handle(&self) -> CloseHandle {
    self.close_handle.clone()
  }
}

#[derive(Clone)]
pub struct CloseHandle {
  handle: Arc<Mutex<Option<(Executor, Vec<oneshot::Sender<()>)>>>,
}

impl CloseHandle {
  ///  <docs ...>
  /// returns `false` if the server was closed earlier
  pub fn close(self) -> bool {
     if let Some((executor, senders)) = self.handle.lock().take() {
        executor.close();
        for s in senders { s.send(()).expect(...) } 
        true
     } else { false }
  }
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how this would work: once the server starts waiting it has to take the lock on the handle, so close() as written above will hang on the lock?

@carllin
Copy link
Author

carllin commented Nov 5, 2018

@tomusdrw The WebSockets server has a separate handle that it joins on in wait(), whereas the wait() implementation in this module requires access to the executor, so we wouldn't be able to call close() on the close handle while another thread is blocked on wait(). Is there a way to construct a similar, separate handle as exists in the WebSockets implementation?

@tomusdrw
Copy link
Contributor

tomusdrw commented Nov 5, 2018

@carllin Indeed WebSockets server runs an extra thread for itself, and in close handle we wait for that thread to finish. With HTTP the proper solution would be to add another oneshot future that is triggered after the serve is finished here:

executor.spawn(future::lazy(move || {

And Server::wait() should actually wait for this, not the entire excutor to be closed.

@tomusdrw
Copy link
Contributor

tomusdrw commented Dec 7, 2018

@carlin I'd like to get that merged, did you consider implementing my proposed changes? We definitely need to support multiple CloseHandles`, that's the whole point of having a clonable handle - otherwise only one handle can be present, cause others will just panic.

@carllin
Copy link
Author

carllin commented Dec 7, 2018 via email

@dvdplm
Copy link
Contributor

dvdplm commented May 24, 2019

@carllin any chance we can get the changes in? If things have piled up on your end we can perhaps take over and finish it up, just let us know! :)

@carllin
Copy link
Author

carllin commented May 24, 2019

@dvdplm, sorry don't have much bandwidth nowadays xD. Of somebody could drag this to the finish line that would be highly appreciated :D. Thanks for all the help!

@dvdplm
Copy link
Contributor

dvdplm commented May 28, 2019

Closing in favour of #437

@dvdplm dvdplm closed this May 28, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants