xaizek / vifm (License: GPLv2+) (since 2018-12-07)
Vifm is a file manager with curses interface, which provides Vi[m]-like environment for managing objects within file systems, extended with some useful ideas from mutt.
Commit 1d3d558f747bd234060940cfb12936b383ba4517

Make VifmJob:errors() wait for finished jobs
Otherwise calling it right after :wait() or :exitcode() might not return
error the command has printed.
Author: xaizek
Author date (UTC): 2024-03-11 13:53
Committer name: xaizek
Committer date (UTC): 2024-03-11 17:30
Parent(s): 8d5672127b4354c447ccbb65358deeb467d8ced2
Signing key: 99DC5E4DB05F6BE2
Tree: 58b16fe55cd66874492c822988a337a8b7470432
File Lines added Lines deleted
ChangeLog.LuaAPI 2 0
data/vim/doc/app/vifm-lua.txt 4 1
src/background.c 45 1
src/background.h 8 0
src/lua/vifmjob.c 5 0
tests/lua/api_jobs.c 0 1
File ChangeLog.LuaAPI changed (mode: 100644) (index 98e2c0eaf..80eb2fd87)
... ... documented in the regular ChangeLog.
32 32 redirected (if it was redirected, i.e. original output wasn't connected to redirected (if it was redirected, i.e. original output wasn't connected to
33 33 a TTY or character device on Windows). Thanks to gruvw. a TTY or character device on Windows). Thanks to gruvw.
34 34
35 Made VifmJob:errors() wait for receiving errors if the job has finished.
36
35 37 Fixed jobs created via vifm.startjob() being displayed with "UNKNOWN" for Fixed jobs created via vifm.startjob() being displayed with "UNKNOWN" for
36 38 description on the job bar. description on the job bar.
37 39
File data/vim/doc/app/vifm-lua.txt changed (mode: 100644) (index 0cdcf9b67..37c744484)
1 *vifm-lua.txt* For Vifm version 1.0 Last change: 2024 Mar 2
1 *vifm-lua.txt* For Vifm version 1.0 Last change: 2024 Mar 11
2 2
3 3 Email for bugs and suggestions: <xaizek@posteo.net> Email for bugs and suggestions: <xaizek@posteo.net>
4 4
 
... ... doesn't wait for arrival of data. Empty if `mergestreams` was set to `true`.
1259 1259 Return:~ Return:~
1260 1260 Returns a string. Returns a string.
1261 1261
1262 Raises an error:~
1263 On failing to wait for errors of a finished job (timeout).
1264
1262 1265 -------------------------------------------------------------------------------- --------------------------------------------------------------------------------
1263 1266 *vifm-l_VifmTab* *vifm-l_VifmTab*
1264 1267
File src/background.c changed (mode: 100644) (index 523dbfc67..6aa8bd66a)
30 30 #include <sys/wait.h> /* waitpid() */ #include <sys/wait.h> /* waitpid() */
31 31 #endif #endif
32 32 #include <signal.h> /* SIG* kill() */ #include <signal.h> /* SIG* kill() */
33 #include <unistd.h> /* execve() fork() setsid() */
33 #include <unistd.h> /* execve() fork() setsid() usleep() */
34 34
35 35 #include <assert.h> /* assert() */ #include <assert.h> /* assert() */
36 36 #include <errno.h> /* errno */ #include <errno.h> /* errno */
 
... ... free_drained_jobs(bg_job_t **jobs)
546 546 if(!j->running) if(!j->running)
547 547 { {
548 548 --j->use_count; --j->use_count;
549 j->erroring = 0;
549 550 *job = j->err_next; *job = j->err_next;
550 551 (void)pthread_spin_unlock(&j->status_lock); (void)pthread_spin_unlock(&j->status_lock);
551 552 continue; continue;
 
... ... add_background_job(pid_t pid, const char cmd[], uintptr_t err, uintptr_t data,
1407 1408 } }
1408 1409
1409 1410 new->running = 1; new->running = 1;
1411 new->erroring = 0;
1410 1412 new->use_count = 0; new->use_count = 0;
1411 1413 new->exit_code = -1; new->exit_code = -1;
1412 1414
 
... ... add_background_job(pid_t pid, const char cmd[], uintptr_t err, uintptr_t data,
1426 1428
1427 1429 if(new->err_stream != NO_JOB_ID) if(new->err_stream != NO_JOB_ID)
1428 1430 { {
1431 new->erroring = 1;
1429 1432 ++new->use_count; ++new->use_count;
1430 1433
1431 1434 if(pthread_mutex_lock(&new_err_jobs_lock) != 0) if(pthread_mutex_lock(&new_err_jobs_lock) != 0)
 
... ... mark_job_finished(bg_job_t *job, int exit_code)
1692 1695 } }
1693 1696 } }
1694 1697
1698 int
1699 bg_job_wait_errors(bg_job_t *job)
1700 {
1701 enum
1702 {
1703 ERROR_SLEEP_US = 50,
1704 ERROR_SLEEP_MAX_US = 50*1000, /* 50ms should be more than enough. */
1705 };
1706
1707 if(job->err_stream == NO_JOB_ID || bg_job_is_running(job))
1708 {
1709 return 0;
1710 }
1711
1712 /* Using active polling with a sleep to avoid adding a mutex and a conditional
1713 * variable to every job with an error stream. The code below shouldn't run
1714 * often. */
1715
1716 int i;
1717 int erroring = 1;
1718 for(i = 0; i < ERROR_SLEEP_MAX_US/ERROR_SLEEP_US && erroring; ++i)
1719 {
1720 if(pthread_spin_lock(&job->status_lock) == 0)
1721 {
1722 erroring = job->erroring;
1723 (void)pthread_spin_unlock(&job->status_lock);
1724 }
1725
1726 if(erroring)
1727 {
1728 poke_error_thread();
1729 usleep(ERROR_SLEEP_US);
1730 }
1731 }
1732
1733 /* In case we've reached here and `erroring` is still non-zero, this could be
1734 * a bug in handling jobs or the system is under heavy load. Either way, we
1735 * probably shouldn't wait here forever, so return an error. */
1736 return erroring;
1737 }
1738
1695 1739 /* Wakes up error thread to process any changes to the jobs. */ /* Wakes up error thread to process any changes to the jobs. */
1696 1740 static void static void
1697 1741 poke_error_thread(void) poke_error_thread(void)
File src/background.h changed (mode: 100644) (index 470e2190d..e74972260)
... ... typedef struct bg_job_t
97 97 /* The lock is meant to guard state-related fields. */ /* The lock is meant to guard state-related fields. */
98 98 pthread_spinlock_t status_lock; pthread_spinlock_t status_lock;
99 99 int running; /* Whether this job is still running. */ int running; /* Whether this job is still running. */
100 int erroring; /* Whether error thread still handles this job. */
100 101 int use_count; /* Count of uses of this job entry. */ int use_count; /* Count of uses of this job entry. */
101 102 int exit_code; /* Exit code of external command. */ int exit_code; /* Exit code of external command. */
102 103
 
... ... int bg_job_was_killed(bg_job_t *job);
209 210 * Returns zero on success, otherwise non-zero is returned. */ * Returns zero on success, otherwise non-zero is returned. */
210 211 int bg_job_wait(bg_job_t *job); int bg_job_wait(bg_job_t *job);
211 212
213 /* Waits until error thread is done with this job. The job might have nothing
214 * to do with the error thread and might actually be running with errors still
215 * comming, but it's possible that the job has exited and not all errors were
216 * processed which is the interesting case. Returns zero on success or non-zero
217 * on failure to wait for error thread to release the job (timeout). */
218 int bg_job_wait_errors(bg_job_t *job);
219
212 220 /* Increases use counter of the job. Doing this prevents object deletion while /* Increases use counter of the job. Doing this prevents object deletion while
213 221 * it's still in use. */ * it's still in use. */
214 222 void bg_job_incref(bg_job_t *job); void bg_job_incref(bg_job_t *job);
File src/lua/vifmjob.c changed (mode: 100644) (index 5d4be919e..aa3b7dedf)
... ... VLUA_API(vifmjob_errors)(lua_State *lua)
387 387 { {
388 388 vifm_job_t *vifm_job = luaL_checkudata(lua, 1, "VifmJob"); vifm_job_t *vifm_job = luaL_checkudata(lua, 1, "VifmJob");
389 389
390 if(bg_job_wait_errors(vifm_job->job) != 0)
391 {
392 return luaL_error(lua, "%s", "Failed to wait for errors of an exited job");
393 }
394
390 395 char *errors = NULL; char *errors = NULL;
391 396 pthread_spin_lock(&vifm_job->job->errors_lock); pthread_spin_lock(&vifm_job->job->errors_lock);
392 397 update_string(&errors, vifm_job->job->errors); update_string(&errors, vifm_job->job->errors);
File tests/lua/api_jobs.c changed (mode: 100644) (index 06c23672e..2d53df28c)
... ... TEST(vifmjob_errors)
42 42 GLUA_STARTS(vlua, "err", GLUA_STARTS(vlua, "err",
43 43 "job = vifm.startjob { cmd = 'echo err 1>&2' }" "job = vifm.startjob { cmd = 'echo err 1>&2' }"
44 44 "job:wait()" "job:wait()"
45 "while #job:errors() == 0 do end\n"
46 45 "print(job:errors())"); "print(job:errors())");
47 46
48 47 GLUA_EQ(vlua, "", GLUA_EQ(vlua, "",
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/vifm

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/vifm

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master