nojb / ocaml-imap

Lwt-compatible IMAP4rev1 client library for OCaml
https://nojb.github.io/ocaml-imap/imap
Other
40 stars 17 forks source link

Can Imap.run block somehow? #10

Closed rgrinberg closed 9 years ago

rgrinberg commented 9 years ago

I'm trying to get a sample that logs into gmail running using async rather than Lwt and it's modeled after imap_shell.ml. However, the code seems to get stuck on the login phase from what appears to be the Imap.run function. This surprises me a little because I don't expect Imap.run to do any IO or block. Am I misunderstanding something. The code in question is here (basically an async port of your handle function with some debugging sprinkled in):

let run (conn : connection) v =
  info "RUN" >>= fun () ->
  let rec loop x =
    info "loop iteration" >>= fun () ->
    match x with
    | `Await_src ->
      info "Trying read" >>= fun () ->
      begin Reader.read conn.reader conn.in_ ~pos:0 ~len:(Bytes.length conn.in_) >>= function
      | `Ok rc ->
        info "Read successful" >>= fun () ->
        info @@ ">>> " ^ (String.sub conn.in_ 0 rc) >>= fun () ->
        Imap.src conn.connection conn.in_ 0 rc;
        loop (Imap.run conn.connection `Await)
      | `Eof -> (info "connection closed" >>= fun () -> Shutdown.exit 0)
      end
    | `Await_dst ->
      info "Trying write" >>= fun () ->
      let rc = Bytes.length conn.out - Imap.dst_rem conn.connection in
      Writer.write conn.writer conn.out ~pos:0 ~len:rc;
      Writer.flushed conn.writer >>= fun () ->
      info "Finished write" >>= fun () ->
      Imap.dst conn.connection conn.out 0 (Bytes.length conn.out);
      loop (Imap.run conn.connection `Await)
    | `Untagged _ as r ->
      info "`Untagged" >>= fun () ->
      return r
    | `Ok _ ->
      info "`Ok" >>= fun () ->
      return `Ok
    | `Error (`Decode_error (`Expected_char _, _, n)) as e ->
      info @@ "ERROR: " ^
        (String.sub conn.in_ n (min 10 (Bytes.length conn.in_ - n))) >>= fun () ->
      return e
    | `Error _ as e ->
      info "`Error" >>= fun () ->
      return e
  in
  let action = Imap.run conn.connection v in
  print_endline "NEVER REACHES THE 2ND TIME";
  loop action

A sample run gives:

2015-06-10 12:45:35.717325-04:00 Info RUN
NEVER REACHES THE 2ND TIME
2015-06-10 12:45:35.717595-04:00 Info loop iteration
2015-06-10 12:45:35.717650-04:00 Info Trying read
2015-06-10 12:45:35.849762-04:00 Info Read successful
2015-06-10 12:45:35.849827-04:00 Info >>> * OK Gimap ready for requests from 66.49.211.216 e3mb140656066ivc

2015-06-10 12:45:35.849997-04:00 Info loop iteration
2015-06-10 12:45:35.850109-04:00 Info `Ok
2015-06-10 12:45:35.850163-04:00 Info Connected..
2015-06-10 12:45:35.850189-04:00 Info RUN

Which to me looks like Imap.run never returns because I don't see the 2nd NEVER REACHES THE 2ND TIME.

Any idea what could be the problem?

Full code is here if you want to try it out: https://gist.github.com/rgrinberg/0e3860109c0f0fa66487

nojb commented 9 years ago

No, Imap.run does not do any I/O and should never block. If it does, it is a bug (an infinite loop somewhere). I'm busy right now but I will study your code later and get back sometime today.

nojb commented 9 years ago

Ok, I found it - you need to add

  Imap.dst conn.connection conn.out 0 (Bytes.length conn.out);

just after between line 78 and line 79:

https://gist.github.com/rgrinberg/0e3860109c0f0fa66487#file-async_imap-ml-L78

Note that this is also in the example code:

https://github.com/nojb/ocaml-imap/blob/master/test/imap_shell.ml#L62

This looks like a bug (handling an output buffer of size 0); have to investigate further for the proper fix.

rgrinberg commented 9 years ago

:trophy:

Awesome. That does the trick. Thanks!

nojb commented 9 years ago

I just pushed a fix for this. Thanks for reporting!

rgrinberg commented 9 years ago

Great, hanks. Will give it a try ASAP.