1use strict;
2use warnings;
3
4BEGIN {
5    use Config;
6    if (! $Config{'useithreads'}) {
7        print("1..0 # SKIP Perl not compiled with 'useithreads'\n");
8        exit(0);
9    }
10}
11
12use ExtUtils::testlib;
13
14use threads;
15
16BEGIN {
17    if (! eval 'use threads::shared; 1') {
18        print("1..0 # SKIP threads::shared not available\n");
19        exit(0);
20    }
21
22    local $SIG{'HUP'} = sub {};
23    my $thr = threads->create(sub {});
24    eval { $thr->kill('HUP') };
25    $thr->join();
26    if ($@ && $@ =~ /safe signals/) {
27        print("1..0 # SKIP Not using safe signals\n");
28        exit(0);
29    }
30
31    require Thread::Queue;
32    require Thread::Semaphore;
33
34    $| = 1;
35    print("1..18\n");   ### Number of tests that will be run ###
36};
37
38
39my $q = Thread::Queue->new();
40my $TEST = 1;
41
42sub ok
43{
44    $q->enqueue(@_);
45
46    while ($q->pending()) {
47        my $ok   = $q->dequeue();
48        my $name = $q->dequeue();
49        my $id   = $TEST++;
50
51        if ($ok) {
52            print("ok $id - $name\n");
53        } else {
54            print("not ok $id - $name\n");
55            printf("# Failed test at line %d\n", (caller)[2]);
56        }
57    }
58}
59
60
61### Start of Testing ###
62ok(1, 'Loaded');
63
64### Thread cancel ###
65
66# Set up to capture warning when thread terminates
67my @errs :shared;
68$SIG{__WARN__} = sub { push(@errs, @_); };
69
70sub thr_func {
71    my $q = shift;
72
73    # Thread 'cancellation' signal handler
74    $SIG{'KILL'} = sub {
75        $q->enqueue(1, 'Thread received signal');
76        die("Thread killed\n");
77    };
78
79    # Thread sleeps until signalled
80    $q->enqueue(1, 'Thread sleeping');
81    sleep(1) for (1..10);
82    # Should not go past here
83    $q->enqueue(0, 'Thread terminated normally');
84    return ('ERROR');
85}
86
87# Create thread
88my $thr = threads->create('thr_func', $q);
89ok($thr && $thr->tid() == 2, 'Created thread');
90threads->yield();
91sleep(1);
92
93# Signal thread
94ok($thr->kill('KILL') == $thr, 'Signalled thread');
95threads->yield();
96
97# Cleanup
98my $rc = $thr->join();
99ok(! $rc, 'No thread return value');
100
101# Check for thread termination message
102ok(@errs && $errs[0] =~ /Thread killed/, 'Thread termination warning');
103
104
105### Thread suspend/resume ###
106
107sub thr_func2
108{
109    my $q = shift;
110
111    my $sema = shift;
112    $q->enqueue($sema, 'Thread received semaphore');
113
114    # Set up the signal handler for suspension/resumption
115    $SIG{'STOP'} = sub {
116        $q->enqueue(1, 'Thread suspending');
117        $sema->down();
118        $q->enqueue(1, 'Thread resuming');
119        $sema->up();
120    };
121
122    # Set up the signal handler for graceful termination
123    my $term = 0;
124    $SIG{'TERM'} = sub {
125        $q->enqueue(1, 'Thread caught termination signal');
126        $term = 1;
127    };
128
129    # Do work until signalled to terminate
130    while (! $term) {
131        sleep(1);
132    }
133
134    $q->enqueue(1, 'Thread done');
135    return ('OKAY');
136}
137
138
139# Create a semaphore for use in suspending the thread
140my $sema = Thread::Semaphore->new();
141ok($sema, 'Semaphore created');
142
143# Create a thread and send it the semaphore
144$thr = threads->create('thr_func2', $q, $sema);
145ok($thr && $thr->tid() == 3, 'Created thread');
146threads->yield();
147sleep(1);
148
149# Suspend the thread
150$sema->down();
151ok($thr->kill('STOP') == $thr, 'Suspended thread');
152
153threads->yield();
154sleep(1);
155
156# Allow the thread to continue
157$sema->up();
158
159threads->yield();
160sleep(1);
161
162# Terminate the thread
163ok($thr->kill('TERM') == $thr, 'Signalled thread to terminate');
164
165$rc = $thr->join();
166ok($rc eq 'OKAY', 'Thread return value');
167
168ok($thr->kill('TERM') == $thr, 'Ignore signal to terminated thread');
169
170exit(0);
171
172# EOF
173