diff options
-rw-r--r-- | autoload.c | 1 | ||||
-rw-r--r-- | eval.c | 2 | ||||
-rw-r--r-- | lib.c | 22 | ||||
-rw-r--r-- | lib.h | 1 | ||||
-rw-r--r-- | stdlib/doc-syms.tl | 1 | ||||
-rw-r--r-- | stdlib/getput.tl | 6 | ||||
-rw-r--r-- | txr.1 | 70 |
7 files changed, 102 insertions, 1 deletions
@@ -473,6 +473,7 @@ static val getput_set_entries(val fun) lit("command-get-buf"), lit("command-put-buf"), lit("command-get-json"), lit("command-put-json"), lit("command-get-jsons"), lit("command-put-jsons"), + lit("close-lazy-streams"), nil }; autoload_set(al_fun, name, fun); @@ -7486,6 +7486,8 @@ void eval_init(void) reg_fun(intern(lit("rt-assert-fail"), system_package), func_n4ov(rt_assert_fail, 3)); + reg_var(lazy_streams_s, nil); + eval_error_s = intern(lit("eval-error"), user_package); case_error_s = intern(lit("case-error"), user_package); uw_register_subtype(eval_error_s, error_s); @@ -120,6 +120,7 @@ val path_not_found_s, path_exists_s, path_permission_s; val warning_s, defr_warning_s, restart_s, continue_s; val gensym_counter_s, length_s; val rplaca_s, rplacd_s, seq_iter_s; +val lazy_streams_s; val nothrow_k, args_k, colon_k, auto_k, fun_k; val wrap_k, reflect_k; @@ -145,6 +146,8 @@ struct cobj_class *seq_iter_cls; static val recycled_conses; +static val lazy_streams_binding; + const seq_kind_t seq_kind_tab[MAXTYPE+1] = { SEQ_NIL, /* NIL */ SEQ_NOTSEQ, /* NUM */ @@ -9516,6 +9519,20 @@ static val lazy_stream_func_nt(val env, val lcons) return prefetched_line; } +static void lazy_stream_register(val stream) +{ + val lazy_streams_dyn_binding = lookup_var(nil, lazy_streams_s); + + if (lazy_streams_dyn_binding) { + if (!lazy_streams_binding) + lazy_streams_binding = lookup_global_var(lazy_streams_s); + + if (lazy_streams_dyn_binding != lazy_streams_binding) { + val list = us_cdr(lazy_streams_dyn_binding); + us_rplacd(lazy_streams_dyn_binding, cons(stream, list)); + } + } +} val lazy_stream_cons(val stream, val no_throw_close) { @@ -9523,6 +9540,7 @@ val lazy_stream_cons(val stream, val no_throw_close) no_throw_close = default_null_arg(no_throw_close); if (real_time_stream_p(stream)) { + lazy_stream_register(stream); return make_lazy_cons(func_f1(stream, if3(no_throw_close, simple_lazy_stream_func_nt, simple_lazy_stream_func))); @@ -9534,6 +9552,7 @@ val lazy_stream_cons(val stream, val no_throw_close) return nil; } + lazy_stream_register(stream); return make_lazy_cons(func_f1(cons(stream, first), if3(no_throw_close, lazy_stream_func_nt, @@ -13243,7 +13262,7 @@ static void obj_init(void) &equal_f, &eq_f, &eql_f, &car_f, &cdr_f, &null_f, &list_f, &identity_f, &identity_star_f, &less_f, &greater_f, - &prog_string, &cobj_hash, + &prog_string, &cobj_hash, &lazy_streams_binding, convert(val *, 0)); nil_string = lit("nil"); @@ -13387,6 +13406,7 @@ static void obj_init(void) rplaca_s = intern(lit("rplaca"), user_package); rplacd_s = intern(lit("rplacd"), user_package); seq_iter_s = intern(lit("seq-iter"), user_package); + lazy_streams_s = intern(lit("*lazy-streams*"), system_package); args_k = intern(lit("args"), keyword_package); nothrow_k = intern(lit("nothrow"), keyword_package); @@ -560,6 +560,7 @@ extern val path_not_found_s, path_exists_s, path_permission_s; extern val warning_s, defr_warning_s, restart_s, continue_s; extern val gensym_counter_s, length_s; extern val rplaca_s, rplacd_s, seq_iter_s; +extern val lazy_streams_s; #define gensym_counter (deref(lookup_var_l(nil, gensym_counter_s))) diff --git a/stdlib/doc-syms.tl b/stdlib/doc-syms.tl index 5d056427..e9685e70 100644 --- a/stdlib/doc-syms.tl +++ b/stdlib/doc-syms.tl @@ -338,6 +338,7 @@ ("clock-t" "N-01B6F219") ("clockid-t" "N-01153D9E") ("close" "D-0016") + ("close-lazy-streams" "N-00B8ACD5") ("close-stream" "N-00596930") ("closedir" "N-01FEE88A") ("closelog" "N-02CEE7EA") diff --git a/stdlib/getput.tl b/stdlib/getput.tl index 0cf751a1..a1be729e 100644 --- a/stdlib/getput.tl +++ b/stdlib/getput.tl @@ -187,3 +187,9 @@ (defun command-put-jsons (cmd seq : flat-p mopt) (with-stream (s (open-command cmd `w@mopt`)) (put-jsons seq s flat-p))) + +(defmacro close-lazy-streams (. body) + ^(let ((sys:*lazy-streams*)) + (unwind-protect + (progn ,*body)) + (mapdo (fun close-stream) sys:*lazy-streams*))) @@ -23796,6 +23796,76 @@ is true, then .meta throw-on-error-p shall be false, and vice versa. +.coNP Macro @ close-lazy-streams +.synb +.mets (close-lazy-streams << body-form *) +.syne +.desc +The +.code close-lazy-streams +macro establishes a dynamic environment in which zero or more +.metn body-form s +are evaluated, yielding the value of the last +.metn body-form , +or else +.code nil +if there are no +.meta body-form +arguments. In this regard, the macro operator resembles +.codn progn . + +The environment established by +.code close-lazy-streams +sets up special monitoring of the the functions +.code lazy-stream-cons +and +.codn get-lines . +Whenever these functions register an I/O stream with a lazy list, that stream is +recorded in a hidden. When the +.code close-lazy-streams +form terminates, it invokes the +.code close-stream +on each stream in the hidden list. + +Note: the +.code close-lazy-streams +macro provides a possible solution for situations in which a body of code, +possibly consisting of nested functions, manipulates lazy lists of lines coming +from from I/O streams, such that these lists are not completely forced. +Incompletely processed lazy lists will not close their associated streams until +they are reclaimed by garbage collection, which could cause the application to +run out of file descriptors. +The +.code close-lazy-streams +macro allows the application to delineate a dynamic contour of code +upon whose termination all such stream associations generated within +that contour will be duly cleaned up. + +.TP* Example: + +Collect list of names of +.code .tl +files which contain the string +.strn "(cons " : + +.verb + ;; Incorrect version: could run out of open files if there are many + ;; files which contain a match processed, because find-if will stop + ;; traversing the list of lines when it finds a match: + (build + (each ((file (glob "*.tl"))) + (if (find-if #/\e(cons / (file-get-lines file)) + (add file)))) + + ;; Addressed with close-lazy-streams: after each iteration, the + ;; stream created by file-get-lines is closed. + (build + (each ((file (glob "*.tl"))) + (close-lazy-streams + (if (find-if #/\e(cons / (file-get-lines file)) + (add file))))) +.brev + .coNP Macro @ delay .synb .mets (delay << expression ) |