allocate_stack: Assertion `size != 0′ failed.

allocate_stack でエラーが出る場合

結論から申しますと「glibcの挙動が変? glibcのソース修正で回避」というメモ書きです

きっかけは普通に sortコマンドを実行しようとしたときに下記のようにエラーが出たことです

$ sort -u /etc/group
sort: allocatestack.c:463: allocate_stack: Assertion `size != 0' failed.
Aborted
$
 

allocate_stack() でのエラーの発生は 以下のコードで検証できました

#include <stdio.h>
#include <pthread.h>

static void* task ( void* p )
{
    return ( NULL );
}

int main ( int argc, char** argv )
{
    pthread_t t;
    pthread_attr_t attr;
    int status;

    status = pthread_attr_init ( &attr );
    if ( status )  return ( status );

    status = pthread_attr_setdetachstate ( &attr, PTHREAD_CREATE_DETACHED );
    if ( status )  return ( status );

    status = pthread_create ( &t, &attr, task, NULL );
    if ( status )  return ( status );

    status = pthread_join ( t, NULL );
    return ( status );
}
 

pthread_create() でスレッドを生成します スレッドは task() を実行して終了します
pthread_join() でスレッドの終了を待ってからメインのスレッドも終了する流れです

上記ソースコードを pthread.c のようなファイル名で保存して

$ gcc -o pthread -lpthread pthread.c
$ ./pthread
pthread: allocatestack.c:470: allocate_stack: Assertion `size != 0' failed.
中止
$
 

となる場合は 今回の事象かもしれません
ちなみに 環境は以下のとおり

OS Linux-2.6.37.1 (x86_64)
GCC gcc-4.5.2
glibc glibc-2.13.1

allocate_stack() は libpthreadにて定義されています つまり glibcです

ソースコードである nptl/allocatestack.c を見ると

460 
461       /* Adjust the stack size for alignment.  */
462       size &= ~__static_tls_align_m1;
463       assert (size != 0);
464 
 

まさしくここがエラーの出ていた部分です
462行目は 確保したいスタックサイズ size に対してアラインメントを取る処理ですが
既にこの時点で size=0 となっており 463行目のチェックに引っかかって異常終了したようです

なぜスタックサイズを size=0 で確保しようとするのか
しばらく glibcのソースを追いかけていましたが

スタックサイズの最小値を決める __defult_stacksize 変数が初期化されず 0 となっている
のが直接の原因のようでした

問題かと思われるのは glibcの libpthread.so です
該当のコードは nptl/nptl-init.c の __pthread_initialize_minimal_internal() です

/* Make sure it meets the minimum size that allocate_stack
   (allocatestack.c) will demand, which depends on the page size.  */
const uintptr_t pagesz = __sysconf (_SC_PAGESIZE);
const size_t minstack = pagesz + __static_tls_size + MINIMAL_REST_STACK;
if (limit.rlim_cur < minstack)
  limit.rlim_cur = minstack;

/* Round the resource limit up to page size.  */
limit.rlim_cur = (limit.rlim_cur + pagesz - 1) & -pagesz;
__default_stacksize = limit.rlim_cur;

コードでは OSの PAGESIZE設定や その他パラメータを積み上げた値として
__default_stacksize = limit.rlim_cur
を決定しています

今回問題となっている glibcでは __pthread_initialize_minimal_internal() 自体が
呼び出されていない状態でした

結局 その後共有ライブラリの挙動を調べた結果
下記のように nptl/nptl-init.c に下記の修正を加えることで エラーが発生しなくなりました

    268 /* This can be set by the debugger before initialization is complete.  *    268 /
    269 static bool __nptl_initial_report_events __attribute_used__;
    270 
    271 void __attribute__((constructor))
    272 __pthread_initialize_minimal_internal (void)
    273 {
    274 #ifndef SHARED
    275   /* Unlike in the dynamically linked case the dynamic linker has not
    276      taken care of initializing the TLS data structures.  */
 

修正とは 271行目の部分に __attribute__((constructor)) を追記したことです
これにより libpthread.so がロードされたタイミングで
__pthread_initialize_minimal_internal() が呼び出され必要な初期化が行われるはずです

上記修正を加えて glibcを再ビルドした結果

$ ./pthread
$
$ sort -u /etc/group
adm::4:root,adm,daemon
audio::18:
bin::1:root,bin,daemon
cdrom::19:
cdrw::80:
console::17:
daemon::2:root,bin,daemon
dialout::20:root
disk::6:root,adm
floppy::11:root
kmem::9:
lp::7:lp
mail::12:mail
man::15:man
mem::8:
news::13:news
nobody::65534:
nofiles:x:200:
nogroup::65533:
portage::250:portage
root::0:root
smmsp:x:209:smmsp
sshd:x:22:
sys::3:root,bin,adm
tape::26:root
tty::5:
usb::85:
users::100:games,admin
utmp:x:406:
uucp::14:uucp
video::27:root
wheel::10:root
$
 

エラーが発生しなくなり スレッドもちゃんと生成されたようです

ちなみに 検証コード pthread.c にも問題があります
下記のように プログラム終了結果を表示すると実はプログラムが異常終了していることが分かります

$ ./pthread ; echo $?
22
$
 

正常終了を示す 0 が返らず エラーコードが表示されています
どこに問題があるのでしょうか
実は pthread_join() は既にスレッドが終了されている場合エラーを返すのです

スレッドの属性を PTHREAD_CREATE_DETACHED でなく PTHREAD_CREATE_JOINABLE とすると
プログラムは正常終了するようになります
PTHREAD_CREATE_JOINABLE は 親スレッドから pthread_join() が呼ばれるまで
リソースを確保しつづけるためです (たとえ先行終了したとしても)
逆に PTHREAD_CREATE_DETACHED はスレッドの終了時に直ちにリソースを解放してしまいます

このような動作の違いのため 下記に注意する必要があります

  • PTHREAD_CREATE_JOINABLE は pthread_join()忘れるとリソース枯渇の原因となる
  • PTHREAD_CREATE_DETACHED は pthread_join()でエラーが返る可能性がある