0
0
mirror of https://github.com/vim/vim.git synced 2025-09-25 03:54:15 -04:00

patch 8.1.1851: crash when sound_playfile() callback plays sound

Problem:    Crash when sound_playfile() callback plays sound.
Solution:   Invoke callback later from event loop.
This commit is contained in:
Bram Moolenaar
2019-08-15 23:05:49 +02:00
parent 5dd143e223
commit 28e67e0c14
9 changed files with 127 additions and 20 deletions

View File

@@ -7692,8 +7692,12 @@ do_sleep(long msec)
} }
#endif #endif
#ifdef FEAT_JOB_CHANNEL #ifdef FEAT_JOB_CHANNEL
if (has_any_channel() && wait_now > 100L) if (has_any_channel() && wait_now > 20L)
wait_now = 100L; wait_now = 20L;
#endif
#ifdef FEAT_SOUND
if (has_any_sound_callback() && wait_now > 20L)
wait_now = 20L;
#endif #endif
ui_delay(wait_now, TRUE); ui_delay(wait_now, TRUE);
#ifdef FEAT_JOB_CHANNEL #ifdef FEAT_JOB_CHANNEL

View File

@@ -666,6 +666,9 @@
#if !defined(FEAT_SOUND) && defined(HAVE_CANBERRA) #if !defined(FEAT_SOUND) && defined(HAVE_CANBERRA)
# define FEAT_SOUND # define FEAT_SOUND
#endif #endif
#if defined(FEAT_SOUND) && defined(HAVE_CANBERRA)
# define FEAT_SOUND_CANBERRA
#endif
/* There are two ways to use XPM. */ /* There are two ways to use XPM. */
#if (defined(HAVE_XM_XPMP_H) && defined(FEAT_GUI_MOTIF)) \ #if (defined(HAVE_XM_XPMP_H) && defined(FEAT_GUI_MOTIF)) \

View File

@@ -4485,6 +4485,10 @@ parse_queued_messages(void)
# endif # endif
# ifdef FEAT_TERMINAL # ifdef FEAT_TERMINAL
free_unused_terminals(); free_unused_terminals();
# endif
# ifdef FEAT_SOUND_CANBERRA
if (has_sound_callback_in_queue())
invoke_sound_callback();
# endif # endif
break; break;
} }

View File

@@ -5998,6 +5998,11 @@ WaitForCharOrMouse(long msec, int *interrupted, int ignore_input)
rest -= msec; rest -= msec;
} }
# endif # endif
# ifdef FEAT_SOUND_CANBERRA
// Invoke any pending sound callbacks.
if (has_sound_callback_in_queue())
invoke_sound_callback();
# endif
# ifdef FEAT_MOUSE_GPM # ifdef FEAT_MOUSE_GPM
gpm_process_wanted = 0; gpm_process_wanted = 0;
avail = RealWaitForChar(read_cmd_fd, msec, avail = RealWaitForChar(read_cmd_fd, msec,

View File

@@ -1,4 +1,7 @@
/* sound.c */ /* sound.c */
int has_any_sound_callback(void);
int has_sound_callback_in_queue(void);
void invoke_sound_callback(void);
void f_sound_playevent(typval_T *argvars, typval_T *rettv); void f_sound_playevent(typval_T *argvars, typval_T *rettv);
void f_sound_playfile(typval_T *argvars, typval_T *rettv); void f_sound_playfile(typval_T *argvars, typval_T *rettv);
void f_sound_stop(typval_T *argvars, typval_T *rettv); void f_sound_stop(typval_T *argvars, typval_T *rettv);

View File

@@ -30,6 +30,16 @@ struct soundcb_S {
static soundcb_T *first_callback = NULL; static soundcb_T *first_callback = NULL;
/*
* Return TRUE when a sound callback has been created, it may be invoked when
* the sound finishes playing. Also see has_sound_callback_in_queue().
*/
int
has_any_sound_callback(void)
{
return first_callback != NULL;
}
static soundcb_T * static soundcb_T *
get_sound_callback(typval_T *arg) get_sound_callback(typval_T *arg)
{ {
@@ -85,6 +95,24 @@ delete_sound_callback(soundcb_T *soundcb)
static ca_context *context = NULL; static ca_context *context = NULL;
// Structure to store info about a sound callback to be invoked soon.
typedef struct soundcb_queue_S soundcb_queue_T;
struct soundcb_queue_S {
soundcb_queue_T *scb_next;
uint32_t scb_id; // ID of the sound
int scb_result; // CA_ value
soundcb_T *scb_callback; // function to call
};
// Queue of callbacks to invoke from the main loop.
static soundcb_queue_T *callback_queue = NULL;
/*
* Add a callback to the queue of callbacks to invoke later from the main loop.
* That is because the callback may be called from another thread and invoking
* another sound function may cause trouble.
*/
static void static void
sound_callback( sound_callback(
ca_context *c UNUSED, ca_context *c UNUSED,
@@ -92,23 +120,58 @@ sound_callback(
int error_code, int error_code,
void *userdata) void *userdata)
{ {
soundcb_T *soundcb = (soundcb_T *)userdata; soundcb_T *soundcb = (soundcb_T *)userdata;
typval_T argv[3]; soundcb_queue_T *scb;
typval_T rettv;
argv[0].v_type = VAR_NUMBER; scb = ALLOC_ONE(soundcb_queue_T);
argv[0].vval.v_number = id; if (scb == NULL)
argv[1].v_type = VAR_NUMBER; return;
argv[1].vval.v_number = error_code == CA_SUCCESS ? 0 scb->scb_next = callback_queue;
callback_queue = scb;
scb->scb_id = id;
scb->scb_result = error_code == CA_SUCCESS ? 0
: error_code == CA_ERROR_CANCELED : error_code == CA_ERROR_CANCELED
|| error_code == CA_ERROR_DESTROYED || error_code == CA_ERROR_DESTROYED
? 1 : 2; ? 1 : 2;
argv[2].v_type = VAR_UNKNOWN; scb->scb_callback = soundcb;
}
call_callback(&soundcb->snd_callback, -1, &rettv, 2, argv); /*
clear_tv(&rettv); * Return TRUE if there is a sound callback to be called.
*/
int
has_sound_callback_in_queue(void)
{
return callback_queue != NULL;
}
delete_sound_callback(soundcb); /*
* Invoke queued sound callbacks.
*/
void
invoke_sound_callback(void)
{
soundcb_queue_T *scb;
typval_T argv[3];
typval_T rettv;
while (callback_queue != NULL)
{
scb = callback_queue;
callback_queue = scb->scb_next;
argv[0].v_type = VAR_NUMBER;
argv[0].vval.v_number = scb->scb_id;
argv[1].v_type = VAR_NUMBER;
argv[1].vval.v_number = scb->scb_result;
argv[2].v_type = VAR_UNKNOWN;
call_callback(&scb->scb_callback->snd_callback, -1, &rettv, 2, argv);
clear_tv(&rettv);
delete_sound_callback(scb->scb_callback);
}
redraw_after_callback(TRUE); redraw_after_callback(TRUE);
} }

View File

@@ -13,7 +13,6 @@ func Test_play_event()
if has('win32') if has('win32')
throw 'Skipped: Playing event with callback is not supported on Windows' throw 'Skipped: Playing event with callback is not supported on Windows'
endif endif
let id = sound_playevent('bell', 'PlayCallback') let id = sound_playevent('bell', 'PlayCallback')
if id == 0 if id == 0
throw 'Skipped: bell event not available' throw 'Skipped: bell event not available'
@@ -21,7 +20,7 @@ func Test_play_event()
" Stop it quickly, avoid annoying the user. " Stop it quickly, avoid annoying the user.
sleep 20m sleep 20m
call sound_stop(id) call sound_stop(id)
sleep 20m sleep 30m
call assert_equal(id, g:id) call assert_equal(id, g:id)
call assert_equal(1, g:result) " sound was aborted call assert_equal(1, g:result) " sound was aborted
endfunc endfunc
@@ -46,6 +45,20 @@ func Test_play_silent()
call assert_true(id2 > 0) call assert_true(id2 > 0)
sleep 20m sleep 20m
call sound_clear() call sound_clear()
sleep 30m
call assert_equal(id2, g:id) call assert_equal(id2, g:id)
call assert_equal(1, g:result) call assert_equal(1, g:result)
" recursive use was causing a crash
func PlayAgain(id, fname)
let g:id = a:id
let g:id_again = sound_playfile(a:fname)
endfunc
let id3 = sound_playfile(fname, {id, res -> PlayAgain(id, fname)})
call assert_true(id3 > 0)
sleep 50m
call sound_clear()
sleep 30m
call assert_true(g:id_again > 0)
endfunc endfunc

View File

@@ -459,12 +459,22 @@ ui_wait_for_chars_or_timer(
} }
if (due_time <= 0 || (wtime > 0 && due_time > remaining)) if (due_time <= 0 || (wtime > 0 && due_time > remaining))
due_time = remaining; due_time = remaining;
# ifdef FEAT_JOB_CHANNEL # if defined(FEAT_JOB_CHANNEL) || defined(FEAT_SOUND_CANBERRA)
if ((due_time < 0 || due_time > 10L) if ((due_time < 0 || due_time > 10L) && (
# ifdef FEAT_GUI # if defined(FEAT_JOB_CHANNEL)
&& !gui.in_use (
# if defined(FEAT_GUI)
!gui.in_use &&
# endif
(has_pending_job() || channel_any_readahead()))
# ifdef FEAT_SOUND_CANBERRA
||
# endif
# endif # endif
&& (has_pending_job() || channel_any_readahead())) # ifdef FEAT_SOUND_CANBERRA
has_any_sound_callback()
# endif
))
{ {
// There is a pending job or channel, should return soon in order // There is a pending job or channel, should return soon in order
// to handle them ASAP. Do check for input briefly. // to handle them ASAP. Do check for input briefly.

View File

@@ -769,6 +769,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
1851,
/**/ /**/
1850, 1850,
/**/ /**/