Module Async_unix.Writer
Writer is Async's main API for output to a file descriptor. It is the analog of Core.Out_channel.
Each writer has an internal buffer, to which Writer.write* adds data. Each writer uses an Async cooperative thread that makes write() system calls to move the data from the writer's buffer to an OS buffer via the file descriptor.
There is no guarantee that the data sync on the other side of the writer can keep up with the rate at which you are writing. If it cannot, the OS buffer will fill up and the writer's cooperative thread will be unable to send any bytes. In that case, calls to Writer.write* will grow the writer's buffer without bound, as long as your program produces data. One solution to this problem is to call Writer.flushed and not continue until that becomes determined, which will only happen once the bytes in the writer's buffer have been successfully transferred to the OS buffer. Another solution is to check Writer.bytes_to_write and not produce any more data if that is beyond some bound.
There are two kinds of errors that one can handle with writers. First, a writer can be closed, which will cause future writes (and other operations) to synchronously raise an exception. Second, the writer's cooperative thread can fail due to a write() system call failing. This will cause an exception to be sent to the writer's monitor, which will be a child of the monitor in effect when the writer is created. One can deal with such asynchronous exceptions in the usual way, by handling the stream returned by Monitor.detach_and_get_error_stream (Writer.monitor writer).
module Id : Core.Unique_idmodule Line_ending : sig ... endval sexp_of_t : t -> Ppx_sexp_conv_lib.Sexp.t
include Async_unix__.Import.Invariant.S with type t := t
val invariant : t Base__.Invariant_intf.inv
val io_stats : Io_stats.tOverall IO statistics for all writers.
val stdout : t Core.Lazy.tstdoutandstderrare writers for file descriptors 1 and 2. They are lazy because we don't want to create them in all programs that happen to link with Async.When either
stdoutorstderris created, they both are created. Furthermore, if they point to the same inode, then they will be the same writer toFd.stdout. This can be confusing, becausefd (force stderr)will beFd.stdout, notFd.stderr. And subsequent modifications ofFd.stderrwill have no effect onWriter.stderr.Unfortunately, the sharing is necessary because Async uses OS threads to do
write()syscalls using the writer buffer. When calling a program that redirects stdout and stderr to the same file, as in:foo.exe >/tmp/z.file 2>&1
if
Writer.stdoutandWriter.stderrweren't the same writer, then they could have threads simultaneously writing to the same file, which could easily cause data loss.
val stderr : t Core.Lazy.t
type buffer_age_limit=[|`At_most of Core.Time.Span.t|`Unlimited]
val bin_shape_buffer_age_limit : Bin_prot.Shape.tval bin_size_buffer_age_limit : buffer_age_limit Bin_prot.Size.sizerval bin_write_buffer_age_limit : buffer_age_limit Bin_prot.Write.writerval bin_writer_buffer_age_limit : buffer_age_limit Bin_prot.Type_class.writerval bin_read_buffer_age_limit : buffer_age_limit Bin_prot.Read.readerval __bin_read_buffer_age_limit__ : (int -> buffer_age_limit) Bin_prot.Read.readerval bin_reader_buffer_age_limit : buffer_age_limit Bin_prot.Type_class.readerval bin_buffer_age_limit : buffer_age_limit Bin_prot.Type_class.tval sexp_of_buffer_age_limit : buffer_age_limit -> Ppx_sexp_conv_lib.Sexp.tval buffer_age_limit_of_sexp : Ppx_sexp_conv_lib.Sexp.t -> buffer_age_limitval __buffer_age_limit_of_sexp__ : Ppx_sexp_conv_lib.Sexp.t -> buffer_age_limit
val create : ?buf_len:int -> ?syscall:[ `Per_cycle | `Periodic of Core.Time.Span.t ] -> ?buffer_age_limit:buffer_age_limit -> ?raise_when_consumer_leaves:bool -> ?line_ending:Line_ending.t -> ?time_source:[> Core.read ] Async_unix__.Import.Time_source.T1.t -> Fd.t -> tcreate ?buf_len ?syscall ?buffer_age_limit fdcreates a new writer. The file descriptorfdshould not be in use for writing by anything else.By default, a write system call occurs at the end of a cycle in which bytes were written. One can supply
~syscall:(`Periodic span)to get better performance. This batches writes together, doing the write system call periodically according to the supplied span.A writer can asynchronously fail if the underlying write syscall returns an error, e.g.,
EBADF,EPIPE,ECONNRESET, ....buffer_age_limitspecifies how backed up you can get before raising an exception. The default is`Unlimitedfor files, and 2 minutes for other kinds of file descriptors. You can supply`Unlimitedto turn off buffer-age checks.raise_when_consumer_leavesspecifies whether the writer should raise an exception when the consumer receiving bytes from the writer leaves, i.e., in Unix, the write syscall returnsEPIPEorECONNRESET. Ifnot raise_when_consumer_leaves, then the writer will silently drop all writes after the consumer leaves, and the writer will eventually fail with a writer-buffer-older-than error if the application remains open long enough.line_endingdetermines hownewlineandwrite_lineterminate lines by default. Ifline_ending = Unixthen end of line is"\n"; ifline_ending = Dosthen end of line is"\r\n". Note thatline_ending = Dosis not equivalent to opening the file in text mode because any "\n" characters being printed by other means (e.g.,write "\n") are still written verbatim (in Unix style).time_sourceis useful in tests to triggerbuffer_age_limit-related conditions, or simply to have the result of (for example)flushed_time_nsagree with your test's synthetic time. It is also used to schedule the`Periodicsyscalls.
val raise_when_consumer_leaves : t -> boolval set_raise_when_consumer_leaves : t -> bool -> unitset_raise_when_consumer_leaves t boolsets theraise_when_consumer_leavesflag oft, which determies howtresponds to a write system call raisingEPIPEandECONNRESET(seecreate).
val set_buffer_age_limit : t -> buffer_age_limit -> unitset_buffer_age_limit t buffer_age_limitreplaces the existing buffer age limit with the new one. This is useful for stdout and stderr, which are lazily created in a context that does not allow applications to specifybuffer_age_limit.
val consumer_left : t -> unit Async_unix__.Import.Deferred.tconsumer_left treturns a deferred that becomes determined whentattempts to write to a pipe that broke because the consumer on the other side left.
val of_out_channel : Core.Out_channel.t -> Fd.Kind.t -> tval open_file : ?append:bool -> ?buf_len:int -> ?syscall:[ `Per_cycle | `Periodic of Core.Time.Span.t ] -> ?perm:int -> ?line_ending:Line_ending.t -> ?time_source:[> Core.read ] Async_unix__.Import.Time_source.T1.t -> string -> t Async_unix__.Import.Deferred.topen_file fileopensfilefor writing and returns a writer for it. It usesUnix_syscalls.openfileto open the file.
val with_file : ?perm:int -> ?append:bool -> ?syscall:[ `Per_cycle | `Periodic of Core.Time.Span.t ] -> ?exclusive:bool -> ?line_ending:Line_ending.t -> ?time_source:[> Core.read ] Async_unix__.Import.Time_source.T1.t -> string -> f:(t -> 'a Async_unix__.Import.Deferred.t) -> 'a Async_unix__.Import.Deferred.twith_file ~file fopensfilefor writing, creates a writert, and runsf tto obtain a deferredd. Whendbecomes determined, the writer is closed. When the close completes, the result ofwith_filebecomes determined with the value ofd.There is no need to call
Writer.flushedto ensure thatwith_filewaits for the writer to be flushed before closing it.Writer.closewill already wait for the flush.
val set_fd : t -> Fd.t -> unit Async_unix__.Import.Deferred.tset_fd t fdsets thefdused bytfor its underlying system calls. It first waits until everything being sent to the currentfdis flushed. Of course, one must understand how the writer works and what one is doing to use this.
val write_gen : ?pos:int -> ?len:int -> t -> 'a -> blit_to_bigstring:('a, Core.Bigstring.t) Core.Blit.blit -> length:('a -> int) -> unitwrite_gen t awritesato writert, withlengthspecifying the number of bytes needed andblit_to_bigstringblittingadirectly into thet's buffer. If one has a type that haslengthandblit_to_bigstringfunctions, like:module A : sig type t val length : t -> int val blit_to_bigstring : (t, Bigstring.t) Blit.blit endthen one can use
write_gento implement a custom analog ofWriter.write, like:module Write_a : sig val write : ?pos:int -> ?len:int -> A.t -> Writer.t -> unit end = struct let write ?pos ?len a writer = Writer.write_gen ~length:A.length ~blit_to_bigstring:A.blit_to_bigstring ?pos ?len writer a endIn some cases it may be difficult to write only part of a value:
module B : sig type t val length : t -> int val blit_to_bigstring : t -> Bigstring.t -> pos:int -> unit endIn these cases, use
write_gen_wholeinstead. It never requires writing only part of a value, although it is potentially less space-efficient. It may waste portions of previously-allocated write buffers if they are too small.module Write_b : sig val write : B.t -> Writer.t -> unit end = struct let write b writer = Writer.write_gen_whole ~length:B.length ~blit_to_bigstring:B.blit_to_bigstring writer b endNote:
write_genandwrite_gen_wholegive you access to the writer's internal buffer. You should not capture it; doing so might lead to errors of the segfault kind.
val write_gen_whole : t -> 'a -> blit_to_bigstring:('a -> Core.Bigstring.t -> pos:int -> unit) -> length:('a -> int) -> unitval write_direct : t -> f:(Core.Bigstring.t -> pos:int -> len:int -> 'a * int) -> 'a optionwrite_direct t ~fgivest's internal buffer tof.posandlendefine the portion of the buffer that can be filled.fmust return a pair(x, written)wherewrittenis the number of bytes written to the buffer atpos.write_directraises ifwritten < 0 || written > len.write_directreturnsSome x, orNoneif the writer is stopped. By usingwrite_directonly, one can ensure that the writer's internal buffer never grows. Look at thewrite_directexpect tests for an example of how this can be used to construct awrite_stringlike function that never grows the internal buffer.
val write_bytes : ?pos:int -> ?len:int -> t -> Core.Bytes.t -> unitwrite ?pos ?len t sadds a job to the writer's queue of pending writes. The contents of the string are copied to an internal buffer beforewritereturns, so clients can do whatever they want withsafter that.
val write : ?pos:int -> ?len:int -> t -> string -> unitval write_bigstring : ?pos:int -> ?len:int -> t -> Core.Bigstring.t -> unitval write_iobuf : ?pos:int -> ?len:int -> t -> ([> Core.read ], _) Iobuf.t -> unitval write_substring : t -> Core.Substring.t -> unitval write_bigsubstring : t -> Core.Bigsubstring.t -> unitval writef : t -> ('a, unit, string, unit) Core.format4 -> 'aval to_formatter : t -> Stdlib.Format.formatterto_formatterreturns an OCaml-formatter that one can print to usingFormat.fprintf. Note that flushing the formatter will only submit all buffered data to the writer, but does not guarantee flushing to the operating system.
val write_char : t -> char -> unitwrite_char t cwrites the character.
val newline : ?line_ending:Line_ending.t -> t -> unitnewline twrites the end-of-line terminator.line_endingcan overridet'sline_ending.
val write_line : ?line_ending:Line_ending.t -> t -> string -> unitwrite_line t s ?line_endingiswrite t s; newline t ?line_ending.
val write_byte : t -> int -> unitwrite_byte t iwrites one 8-bit integer (as the single character with that code). The given integer is taken modulo 256.
module Terminate_with : sig ... endval write_sexp : ?hum:bool -> ?terminate_with:Terminate_with.t -> t -> Core.Sexp.t -> unitwrite_sexp t sexpwrites totthe string representation ofsexp, possibly followed by a terminating character as perTerminate_with. With~terminate_with:Newline, the terminating character is a newline. With~terminate_with:Space_if_needed, if a space is needed to ensure that the sexp reader knows that it has reached the end of the sexp, then the terminating character will be a space; otherwise, no terminating character is added. A terminating space is needed if the string representation doesn't end in')'or'"'.
val write_bin_prot : t -> 'a Bin_prot.Type_class.writer -> 'a -> unitwrite_bin_protwrites out a value using its bin_prot sizer/writer pair. The format is the "size-prefixed binary protocol", in which the length of the data is written before the data itself. This is the format thatReader.read_bin_protreads.
val write_bin_prot_no_size_header : t -> size:int -> 'a Bin_prot.Write.writer -> 'a -> unitWrites out a value using its bin_prot writer. Unlike
write_bin_prot, this doesn't prefix the output with the size of the bin_prot blob.sizeis the expected size. This function will raise if the bin_prot writer writes an amount other thansizebytes.
val schedule_bigstring : t -> ?pos:int -> ?len:int -> Core.Bigstring.t -> unitschedule_bigstring t bstrschedules a write of bigstringbstr. It is not safe to change the bigstring until the writer has been successfully flushed or closed after this operation.
val schedule_bigsubstring : t -> Core.Bigsubstring.t -> unitval schedule_iobuf_peek : t -> ?pos:int -> ?len:int -> ([> Core.read ], _) Iobuf.t -> unitschedule_iobuf_peekis likeschedule_bigstring, but for an iobuf. It is not safe to change the iobuf until the writer has been successfully flushed or closed after this operation.
val schedule_iobuf_consume : t -> ?len:int -> ([> Core.read ], Iobuf.seek) Iobuf.t -> unit Async_unix__.Import.Deferred.tschedule_iobuf_consumeis likeschedule_iobuf_peek, and additionally advances the iobuf beyond the portion that has been written. Until the result is determined, it is not safe to assume whether the iobuf has been advanced yet or not.
module Destroy_or_keep : sig ... endval schedule_iovec : ?destroy_or_keep:Destroy_or_keep.t -> t -> Core.Bigstring.t Core.Unix.IOVec.t -> unitschedule_iovec t iovecschedules a write of I/O-vectoriovec. It is not safe to change the bigstrings underlying the I/O-vector until the writer has been successfully flushed or closed after this operation.
val schedule_iovecs : t -> Core.Bigstring.t Core.Unix.IOVec.t Core.Queue.t -> unitschedule_iovecs t iovecslikeschedule_iovec, but takes a whole queueiovecsof I/O-vectors as argument. The queue is guaranteed to be empty when this function returns and can be modified. It is not safe to change the bigstrings underlying the I/O-vectors until the writer has been successfully flushed or closed after this operation.
module Flush_result : sig ... endval flushed_or_failed_with_result : t -> Flush_result.t Async_unix__.Import.Deferred.tflushed_or_failed_with_result treturns a deferred that will become determined when all prior writes complete (i.e. thewrite()system call returns), or when any of them fail.Handling the
Errorcase can be tricky due to the following race: the result gets determined concurrently with the exception propagation through the writer's monitor. The caller needs to make sure that the program behavior does not depend on which signal propagates first.
val flushed_or_failed_unit : t -> unit Async_unix__.Import.Deferred.tflushed_or_failed_unit treturns a deferred that will become determined when all prior writes complete, or when any of them fail.Unlike
flushed_or_failed_with_result, its return value gives you no indication of which happened. In theErrorcase, the result will be determined in parallel with the error propagating to the writer's monitor. The caller should robustly handle either side winning that race.
val flushed : t -> unit Async_unix__.Import.Deferred.tflushed treturns a deferred that will become determined when all prior writes complete (i.e. thewrite()system call returns). If a prior write fails, then the deferred will never become determined.It is OK to call
flushed tafterthas been closed.
val flushed_time : t -> Core.Time.t Async_unix__.Import.Deferred.tval flushed_time_ns : t -> Core.Time_ns.t Async_unix__.Import.Deferred.tval fsync : t -> unit Async_unix__.Import.Deferred.tval fdatasync : t -> unit Async_unix__.Import.Deferred.tval send : t -> string -> unitsendwrites a string to the writer that can be read back usingReader.recv.
val monitor : t -> Async_unix__.Import.Monitor.tmonitor treturns the writer's monitor.
val close : ?force_close:unit Async_unix__.Import.Deferred.t -> t -> unit Async_unix__.Import.Deferred.tclose ?force_close twaits for the writer to be flushed, and then callsUnix.closeon the underlying file descriptor.force_closecauses theUnix.closeto happen even if the flush hangs. By defaultforce_closeisDeferred.never ()for files andafter (sec 5)for other types of file descriptors (e.g., sockets). If the close is forced, data in the writer's buffer may not be written to the file descriptor. You can check this by callingbytes_to_writeafterclosefinishes.WARNING:
force_closewill not reliably stop any write that is in progress. If there are any in-flight system calls, it will wait for them to finish, which includeswritev, which can legitimately block forever.closewill raise an exception if theUnix.closeon the underlying file descriptor fails.You must call
closeon a writer in order to close the underlying file descriptor. Not doing so will cause a file descriptor leak. It also will cause a space leak, because until the writer is closed, it is held on to in order to flush the writer on shutdown.It is an error to call other operations on
tafterclose thas been called, except that calls ofclosesubsequent to the original call toclosewill return the same deferred as the original call.close_started tbecomes determined as soon ascloseis called.close_finished tbecomes determined aftert's underlying file descriptor has been closed, i.e., it is the same as the result ofclose.close_finisheddiffers fromclosein that it does not have the side effect of initiating a close.is_closed treturnstrueiffclose thas been called.is_open tisnot (is_closed t)with_close t ~frunsf (), and closestafterffinishes or raises.
val close_started : t -> unit Async_unix__.Import.Deferred.tval close_finished : t -> unit Async_unix__.Import.Deferred.tval is_closed : t -> boolval is_open : t -> boolval with_close : t -> f:(unit -> 'a Async_unix__.Import.Deferred.t) -> 'a Async_unix__.Import.Deferred.tval can_write : t -> boolcan_write treturnstrueif calls towrite*functions ontare allowed. Ifis_open tthencan_write t. But one can haveis_closed tandcan_write t, during the time afterclose tbefore closing has finished.
val is_stopped_permanently : t -> boolErrors raised within the writer can stop the background job that flushes out the writer's buffers.
is_stopped_permanentlyreturnstruewhen the background job has stopped.stopped_permanentlybecomes determined when the background job has stopped.
val stopped_permanently : t -> unit Async_unix__.Import.Deferred.tval with_flushed_at_close : t -> flushed:(unit -> unit Async_unix__.Import.Deferred.t) -> f:(unit -> 'a Async_unix__.Import.Deferred.t) -> 'a Async_unix__.Import.Deferred.tIn addition to flushing its internal buffer prior to closing, a writer keeps track of producers that are feeding it data, so that when
Writer.closeis called, it does the following:- requests that the writer's producers flush their data to it
- flushes the writer's internal buffer
- calls
Unix.closeon the writer's underlying file descriptor
with_flushed_at_close t ~flushed ~fcallsfand addsflushedto the set of producers that should be flushed-at-close, for the duration off.
val bytes_to_write : t -> intbytes_to_write treturns how many bytes have been requested to write but have not yet been written.
val bytes_written : t -> Core.Int63.tbytes_written treturns how many bytes have been written.
val bytes_received : t -> Core.Int63.tbytes_received treturns how many bytes have been received by the writer. As long as the writer is running,bytes_received = bytes_written + bytes_to_write.
val with_file_atomic : ?temp_file:string -> ?perm:Core.Unix.file_perm -> ?fsync:bool -> ?time_source:[> Core.read ] Async_unix__.Import.Time_source.T1.t -> string -> f:(t -> 'a Async_unix__.Import.Deferred.t) -> 'a Async_unix__.Import.Deferred.twith_file_atomic ?temp_file ?perm ?fsync file ~fcreates a writer to a temp file, feeds that writer tof, and when the result offbecomes determined, atomically moves (usingUnix.rename) the temp file tofile. Iffilecurrently exists, it will be replaced, even if it is read-only. The temp file will befile(ortemp_fileif supplied) suffixed by a unique random sequence of six characters. The temp file may need to be removed in case of a crash so it may be prudent to choose a temp file that can be easily found by cleanup tools.If
fsyncistrue, the temp file will be flushed to disk before it takes the place of the target file, thus guaranteeing that the target file will always be in a sound state, even after a machine crash. Since synchronization is extremely slow, this is not the default. Think carefully about the event of machine crashes and whether you may need this option!We intend for
with_file_atomicto mimic the behavior of theopensystem call, so iffiledoes not exist, we will apply the current umask toperm(the effective permissions becomeperm land lnot umask, seeman 2 open). However, iffiledoes exist andpermis specified, we do something different fromopensystem call: we override the permission withperm, ignoring the umask. This means that if you create and then immediately overwrite the file withwith_file_atomic ~perm, then the umask will be honored the first time and ignored the second time. Ifpermis not specified, then any existing file permissions are preserved.If
fcloses the writer passed to it,with_file_atomicraises and does not createfile.
val save : ?temp_file:string -> ?perm:Core.Unix.file_perm -> ?fsync:bool -> string -> contents:string -> unit Async_unix__.Import.Deferred.tsaveis a special case ofwith_file_atomicthat atomically writes the given string to the specified file.
val save_lines : ?temp_file:string -> ?perm:Core.Unix.file_perm -> ?fsync:bool -> string -> string list -> unit Async_unix__.Import.Deferred.tsave_lines file lineswrites all lines inlinestofile, with each line followed by a newline.
val save_sexp : ?temp_file:string -> ?perm:Core.Unix.file_perm -> ?fsync:bool -> ?hum:bool -> string -> Core.Sexp.t -> unit Async_unix__.Import.Deferred.tsave_sexpis a special case ofwith_file_atomicthat atomically writes the given sexp to the specified file.save_sexp t sexpwritessexptot, followed by a newline. To read a file produced usingsave_sexp, one would typically useReader.load_sexp, which deals with the additional whitespace and works nicely with converting the sexp to a value.
val save_sexps : ?temp_file:string -> ?perm:Core.Unix.file_perm -> ?fsync:bool -> ?hum:bool -> string -> Core.Sexp.t list -> unit Async_unix__.Import.Deferred.tsave_sexpsworks similarly tosave_sexp, but saves a sequence of sexps instead, separated by newlines. There is a correspondingReader.load_sexpsfor reading back in.
val save_bin_prot : ?temp_file:string -> ?perm:Core.Unix.file_perm -> ?fsync:bool -> string -> 'a Bin_prot.Type_class.writer -> 'a -> unit Async_unix__.Import.Deferred.tsave_bin_prot t bin_writer 'ais a special case ofwith_file_atomicthat writes'atotusing its bin_writer, in the size-prefixed format, likewrite_bin_prot. To read a file produced usingsave_bin_prot, one would typically useReader.load_bin_prot.
val transfer' : ?stop:unit Async_unix__.Import.Deferred.t -> ?max_num_values_per_read:int -> t -> 'a Async_unix__.Import.Pipe.Reader.t -> ('a Core.Queue.t -> unit Async_unix__.Import.Deferred.t) -> unit Async_unix__.Import.Deferred.ttransfer' t pipe_r frepeatedly reads values frompipe_rand feeds them tof, which should in turn write them tot. It provides pushback topipe_rby not reading whentcannot keep up with the data being pushed in.By default, each read from
pipe_rreads all the values inpipe_r. One can supplymax_num_values_per_readto limit the number of values per read.The
transfer'stops and the result becomes determined whenstopbecomes determined, whenpipe_rreaches its EOF, whentis closed, or whent's consumer leaves. In the latter two cases,transfer'closespipe_r.transfer'causesPipe.flushedonpipe_r's writer to ensure that the bytes have been flushed totbefore returning. It also waits onPipe.upstream_flushedat shutdown.transfer t pipe_r fis equivalent to:transfer' t pipe_r (fun q -> Queue.iter q ~f; return ())
val transfer : ?stop:unit Async_unix__.Import.Deferred.t -> ?max_num_values_per_read:int -> t -> 'a Async_unix__.Import.Pipe.Reader.t -> ('a -> unit) -> unit Async_unix__.Import.Deferred.tval pipe : t -> string Async_unix__.Import.Pipe.Writer.tpipe treturns the writing end of a pipe attached totthat pushes back whentcannot keep up with the data being pushed in. Closing the pipe does not closet.
val of_pipe : ?time_source:[> Core.read ] Async_unix__.Import.Time_source.T1.t -> Core.Info.t -> string Async_unix__.Import.Pipe.Writer.t -> (t * [ `Closed_and_flushed_downstream of unit Async_unix__.Import.Deferred.t ]) Async_unix__.Import.Deferred.tof_pipe info pipe_wreturns a writertsuch that data written totwill appear onpipe_w. If eithertorpipe_ware closed, the other is closed as well.of_pipeis implemented by attachingtto the write-end of a Unix pipe, and shuttling bytes from the read-end of the Unix pipe topipe_w.
val behave_nicely_in_pipeline : ?writers:t list -> unit -> unitbehave_nicely_in_pipeline ~writers ()causes the program to silently exit with status 0 if any of the consumers ofwritersgo away. It also sets the buffer age to unlimited, in case there is a human (e.g., usingless) on the other side of the pipeline.
val set_synchronous_out_channel : t -> Core.Out_channel.t -> unit Async_unix__.Import.Deferred.tset_synchronous_out_channel t out_channelwaits untilbyte_to_write t = 0, and then mutatestso that all future writes totsynchronously callOut_channel.output*functions to send data to the OS immediately.set_synchronous_out_channelis used by expect tests to ensure that the interleaving between calls toCore.printf(and similar IO functions) andAsync.printfgenerates output with the same interleaving.set_synchronous_out_channelis idempotent.
val using_synchronous_backing_out_channel : t -> boolusing_synchronous_backing_out_channel t = trueif writes totare being done synchronously, e.g., due toset_synchronous_out_channel,set_synchronous_backing_out_channel,use_synchronous_stdout_and_stderr.
val clear_synchronous_out_channel : t -> unitclear_synchronous_out_channel trestorestto its normal state, with the background writer asynchronously feeding data to the OS.clear_synchronous_out_channelis idempotent.
val with_synchronous_out_channel : t -> Core.Out_channel.t -> f:(unit -> 'a Async_unix__.Import.Deferred.t) -> 'a Async_unix__.Import.Deferred.tval use_synchronous_stdout_and_stderr : unit -> unit Async_unix__.Import.Deferred.tuse_synchronous_stdout_and_stderr ()causes all subsequent writes to stdout and stderr to occur synchronously (after any pending writes have flushed).This ensures
printf-family writes happen immediately, which avoids two common sources of confusion:- unexpected interleaving of
Core.printfandAsync.printfcalls; and Async.printfcalls that don't get flushed before an application exits
The disadvantages are:
- this makes writes blocking, which can delay unrelated asynchronous jobs until the consumer stops pushing back; and
- the errors raised by write are different and it won't respect
behave_nicely_in_pipelineanymore
- unexpected interleaving of
module Backing_out_channel : sig ... endBacking_out_channelgeneralizesOut_channelto a narrow interface that can be used to collect strings, etc.
val set_synchronous_backing_out_channel : t -> Backing_out_channel.t -> unit Async_unix__.Import.Deferred.tval with_synchronous_backing_out_channel : t -> Backing_out_channel.t -> f:(unit -> 'a Async_unix__.Import.Deferred.t) -> 'a Async_unix__.Import.Deferred.t