123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122(**
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the "hack" directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*)(**
* This tool allows for marshaling directly over file descriptors (instead of
* ocaml "channels") to avoid buffering so that we can safely use marshaling
* and libancillary together.
*
* The problem:
* Ocaml's marshaling is done over channels, which have their own internal
* buffer. This means after reading a marshaled object from a channel, the
* FD's position is not guaranteed to be pointing to the beginning of the
* next marshaled object (but instead points to the position after the
* buffered read). So another process cannot receive this FD (over
* libancillary) to start reading the next object.
*
* The solution:
* Start each message with a fixed-size preamble that describes the
* size of the payload to read. Read precisely that many bytes directly
* from the FD avoiding Ocaml channels entirely.
*)exceptionInvalid_Int_Size_ExceptionexceptionPayload_Size_Too_Large_ExceptionexceptionMalformed_Preamble_ExceptionexceptionWriting_Preamble_ExceptionexceptionWriting_Payload_ExceptionexceptionReading_Preamble_ExceptionexceptionReading_Payload_Exception(* We want to marshal exceptions (or at least their message+stacktrace) over *)(* the wire. This type ensures that no one will attempt to pattern-match on *)(* the thing we marshal: 'Values of extensible variant types, for example *)(* exceptions (of extensible type exn), returned by the unmarhsaller should *)(* not be pattern-matched over, because unmarshalling does not preserve the *)(* information required for matching their constructors.' *)(* https://caml.inria.fr/pub/docs/manual-ocaml/libref/Marshal.html *)typeremote_exception_data={message:string;stack:string;}letpreamble_start_sentinel='\142'(** Size in bytes. *)letpreamble_core_size=4letexpected_preamble_size=preamble_core_size+1(** Payload size in bytes = 2^31 - 1. *)letmaximum_payload_size=(1lsl(preamble_core_size*8))-1letget_preamble_core(size:int)=(* We limit payload size to 2^31 - 1 bytes. *)ifsize>=maximum_payload_sizethenraisePayload_Size_Too_Large_Exception;letrecloopi(remainder:int)acc=ifi<0thenaccelseloop(i-1)(remainder/256)(Bytes.setacci(Char.chr(remaindermod256));acc)inloop(preamble_core_size-1)size(Bytes.createpreamble_core_size)letmake_preamble(size:int)=letpreamble_core=get_preamble_coresizeinletpreamble=Bytes.create(preamble_core_size+1)inBytes.setpreamble0preamble_start_sentinel;Bytes.blitpreamble_core0preamble14;preambleletparse_preamblepreamble=if(Bytes.lengthpreamble)<>expected_preamble_size||(Bytes.getpreamble0)<>preamble_start_sentinelthenraiseMalformed_Preamble_Exception;letrecloopiacc=ifi>=5thenaccelseloop(i+1)((acc*256)+(int_of_char(Bytes.getpreamblei)))inloop10letto_fd_with_preamblefdobj=letflag_list=[]inletpayload=Marshal.to_bytesobjflag_listinletsize=Bytes.lengthpayloadinletpreamble=make_preamblesizeinletpreamble_bytes_written=Unix.writefdpreamble0expected_preamble_sizeinifpreamble_bytes_written<>expected_preamble_sizethenraiseWriting_Preamble_Exception;letbytes_written=Unix.writefdpayload0sizeinifbytes_written<>sizethenraiseWriting_Payload_Exception;()letrecread_payloadfdbufferoffsetto_read=ifto_read=0thenoffsetelsebeginletbytes_read=Unix.readfdbufferoffsetto_readinifbytes_read=0thenoffsetelsebeginread_payloadfdbuffer(offset+bytes_read)(to_read-bytes_read)endendletfrom_fd_with_preamblefd=letpreamble=Bytes.createexpected_preamble_sizeinletbytes_read=Unix.readfdpreamble0expected_preamble_sizeinif(bytes_read=0)(* Unix manpage for read says 0 bytes read indicates end of file. *)thenraiseEnd_of_fileelseif(bytes_read<>expected_preamble_size)then(Printf.eprintf"Error, only read %d bytes for preamble.\n"bytes_read;raiseReading_Preamble_Exception);letpayload_size=parse_preamblepreambleinletpayload=Bytes.createpayload_sizeinletpayload_size_read=read_payloadfdpayload0payload_sizeinif(payload_size_read<>payload_size)thenraiseReading_Payload_Exception;Marshal.from_bytespayload0