Black Friday = Pricewatch Bekijk onze selectie van de beste Black Friday-deals en voorkom een miskoop.

[C] write() thread safe?

Pagina: 1
Acties:

  • wacco
  • Registratie: Augustus 2002
  • Laatst online: 21-03-2023

wacco

cli, hlt.

Topicstarter
Ik wil voor een nieuw projectje een stapel threads via een fastcgi interface laten kletsen met de webserver daemon. Nu is er wel een multithread voorbeeld welke werkt met de fastcgi library, maar wilde even zeker weten of deze threadsafe was en ging wat graven. Ik vond het namelijk wat verdacht dat het ding zonder enige locks kan werken in de library zelf, er is immers maar 1 socket uiteindelijk waar alles overheen moet.

Lang verhaal kort; die library schrijft (onafhankelijke - dus de volgorde maakt niet uit, zolang ze maar 'in één keer' weggeschreven worden) buffers weg via een enkele write() naar de socket file descriptor. Is write gegarandeerd threadsafe? Het enige relevante wat ik kon opgraven via google was deze LWN pagina, maar die is uit 2006 en weet niet of de patch uiteindelijk daadwerkelijk in de linux kernel is beland. Nu wil ik uiteindelijk straks wel platform onafhankelijk zijn (zit straks te devven op een mac) dus als er soortgelijke "gotcha's" zijn onder andere besturingsystemen wil ik dat nu eigenlijk wel alvast weten. :)

Verder vertelt de manpage op m'n mac niets over thread safety, en zijn er geen garanties voor zover ik kon zien dat alle data in één keer wordt weggeschreven (ik wil wel gewoon blocking IO doen verder - ik zie niet direct een voordeel om nonblocking te gebruiken als ik al threads toe pas) dus lijkt me dat het simpelweg aanroepen van een write op een gedeelde fd in meerdere threads niet safe is.

Heb ik het fout? Wel fijn om te weten voordat ik daadwerkelijk begin met eromheen werken via een extra mutex enzo. :P
Ja, we hebben het maar over een enkele mutex. Maar het blijft lomp als die helemaal niet nodig is en het frustreert me dat ik geen betrouwbare info erover kan vinden.

Spolap: Interactive webcomic


  • wacco
  • Registratie: Augustus 2002
  • Laatst online: 21-03-2023

wacco

cli, hlt.

Topicstarter
Ok, even de login info opgezocht van een dual core opteron bak, en deze geeft met dit stukje code het volgende resultaat;
[16:08:17] [meeuwi10:~]$ ./a.out > output
[16:08:26] [meeuwi10:~]$ cat output
hi from 1
hi from 0
[16:08:29] [meeuwi10:~]$ uname -aLinux oege.ie.hva.nl 2.6.24-gentoo-r5 #1 SMP Thu Apr 10 13:45:57 CEST 2008 i686 Dual-Core AMD Opteron(tm) Processor 2218 AuthenticAMD GNU/Linux
Die patch waar LWN het over had is dus nooit in de linux kernel terecht gekomen en write() (en dus ook indirect fastcgi) is dus niet threadsafe. Ook al hebben ze een multithread demo erbij geleverd. Joy :'(

Dan maar even een andere richting aan dit topic geven, weet iemand een fastcgi C library welke dan wel threadsafe is? Anders moet ik het protocol zelf gaan zitten implementeren. :/

Spolap: Interactive webcomic


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 13-09 00:05
Wat bedoel jij met threadsafe? Als ik de output zie, dan kan ik op geen enkele manier daaruit afleiden dat er een threadsafety probleem is. Daarom vermoed ik dat jij een eigen definitie hanteert.

[edit]
Misschien iets duidelijker zijn: het is mij niet duidelijk waarom het thread-safety probleem 'm in write() moet zitten. Geen enkele van de aangeroepen file functies lijkt me gesynchroniseerd.

[ Voor 34% gewijzigd door MSalters op 28-08-2008 13:59 ]

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • Janoz
  • Registratie: Oktober 2000
  • Laatst online: 15-11 11:40

Janoz

Moderator Devschuur®

!litemod

De beschrijving achter de link vertelt mij dat het 10 regels zouden moeten zijn.

Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'


  • wacco
  • Registratie: Augustus 2002
  • Laatst online: 21-03-2023

wacco

cli, hlt.

Topicstarter
Ik had misschien even de bron kunnen quoten ja. :)
The small C program below the problem - in a nutshell, if multiple threads
write() to STDOUT_FILENO, and stdout has been redirected to a file, then some
of the output lines get lost.
Er hadden dus inderdaad 10 regels moeten worden weergegeven, en er zijn er maar 2 uiteindelijk in de output terecht gekomen. Het probleem is hier dat write niet geynchroniseerd wordt tussen de threads omdat aangenomen wordt dat het besturingssysteem dit wel regelt.
Vreemd genoeg werkt het dus wel als de output direct naar de shell wordt geprint, dus vraag ik me af of ik dit probleem tegen zal komen als ik write naar een socket. Heb niet echt zin om heel fastcgi te moeten gaan implementeren..
Even de culprit-code:
C:
1
2
3
4
5
 for (i =0 ; i < 10; i++) {
    int ret = pthread_create(&(id[ i ]),NULL,&start,(void*)i);
/* En dan in start() ergens.. */
  sprintf(msg,"hi from %i\n",(int)arg);
  res = write(STDOUT_FILENO,msg,strlen(msg));

Waar in mijn geval dus STDOUT_FILENO m'n socket fd zal zijn. Kan ik op bepaalde OSen deze bug verwachten of niet?

Spolap: Interactive webcomic


  • pkuppens
  • Registratie: Juni 2007
  • Laatst online: 14-11 17:32
Wat weerhoudt je om een mutex semafoor op je socket te zetten?

Zie:
http://www.csc.villanova.edu/~mdamian/threads/posixsem.html

Oftwel je code wordt 5 regels langer.
Een globale definitie, een initialisatie,
een sem_wait voor en een sem_post na je write,
en nog ergens een destroy.

Vertaald naar je fastcgi:
pthread_mutex_lock(&counts_mutex);
pthread_mutex_unlock(&counts_mutex);

[ Voor 19% gewijzigd door pkuppens op 28-08-2008 15:29 ]


  • wacco
  • Registratie: Augustus 2002
  • Laatst online: 21-03-2023

wacco

cli, hlt.

Topicstarter
Dat weet ik wel, maar de code in kwestie zit in een library (fastcgi) en die wil ik niet gaan aanpassen. Ok, ik kan hem wel aanpassen, maar waarom doet niemand anders dit of heeft niemand anders dit gedaan in al die jaren dat de library al bestaat? En waarom zit er wel een multithread voorbeeld bij die code? Ben ik de eerste die multithreading doet met die library, ook al zit er zelfs een multithreading voorbeeld bij? :?

Het is niet de eerste keer dat ik met meerdere threads werk verder, maar toch bedankt :>

Spolap: Interactive webcomic


  • NMe
  • Registratie: Februari 2004
  • Laatst online: 09-09 13:58

NMe

Quia Ego Sic Dico.

Waar hoort mijn topic?

Je zit met een praktisch probleem in een specifieke taal. Software Engineering & Architecture gaat over de ontwerpfase van het programmeren, niet over het implementeren. ;)

SEA>>PRG

'E's fighting in there!' he stuttered, grabbing the captain's arm.
'All by himself?' said the captain.
'No, with everyone!' shouted Nobby, hopping from one foot to the other.


  • Soultaker
  • Registratie: September 2000
  • Laatst online: 16-11 23:41
Mijn reactie na het lezen van de eerste twee alinea's was: de kernel garandeert deze thread safety wel. Blijkbaar is de Linux kernel echter niet POSIX-compliant op dit punt; beetje jammer; Linux heeft daar wel vaker last van, maar de laaste tien jaar hebben ze juist ook een hoop incompatibiliteiten weggewerkt. Andere OSes (BSD-based b.v.) hebben hier bijvoorbeeld geen last van, en ik vermoed dat zelfs Windows het goed doet.

Wat betreft je concrete probleem: ik heb twee observaties. Ten eerste staat in de man-page van write het volgende:
For a seekable file (i.e., one to which lseek(2) may be applied, for example, a regular file) writing takes place at the current file offset, and the file offset is incremented by the number of bytes actually written.

If the file was open(2)ed with O_APPEND, the file offset is first set to the end of the file before writing. The adjustment of the file offset and the write operation are performed as an atomic step.
De eerste situatie (dus file openen zonder O_APPEND) is wat het probleem veroorzaakt, en deze situatie (zoals ook uit de bug reports waar je aan refereert blijkt) treedt alleen op bij seekable files, en dus niet bij sockets en pipes. Als FastCGI daar ongesynchroniseerd naar schrijft, zou dat dus ook onder Linux goed moeten gaan.

De tweede observatie is dat je met de FastCGI library niet direct naar een file descriptor schrijft, maar naar een stream object. Als er locking nodig zou zijn, zou de FastCGI library dat dus prima kunnen implementeren in de write-methoden op die stream. Verder is FastCGI juist ontwikkeld om met een hoge mate van parallelliteit efficient requests te kunnen afhandelen, dus ik neem aan dat het scenario van meerdere concurrent writes niet nieuw voor ze is. Ik zou dus denken dat als locking nodig is om de correcte werking van de library te garanderen, dat wel geïmplementeerd is. Ongeacht of er nu wel of niet gelockt wordt, zou ik er dus vanuit gaan dat als je de FastCGI library correct gebruikt, de library eventuele locking verder afhandelt.

Conclusie: gebruik gewoon de officiële FastCGI library, waarschijnlijk is die wel thread safe. Sowieso kun je beter locking in de library patchen (als je denkt dat dat nodig is) dan een library opnieuw implementeren.

[ Voor 7% gewijzigd door Soultaker op 28-08-2008 16:22 ]


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Garandeert POSIX thread-safety als je naar dezelfde file schrijft? Dat lijkt me vreemd. Het lijkt me eerder om twee concurrent writes naar verschillende files te gaan, i.e. geen unsafe buffering schemes?
die library schrijft (onafhankelijke - dus de volgorde maakt niet uit, zolang ze maar 'in één keer' weggeschreven worden) buffers weg via een enkele write()
Maar write() is uberhaupt geen atomic operation, toch? Dus wat is dan de relevantie van dat voorbeeld? Op die manier is het sowieso niet veilig: buffers kunnen door elkaar heen komen te staan.

[ Voor 47% gewijzigd door Zoijar op 28-08-2008 16:32 ]


  • Soultaker
  • Registratie: September 2000
  • Laatst online: 16-11 23:41
Volgens de POSIX spec zijn writes wel atomair (tenminste, als op de mailing list goed gequote is):
2.9.7 Thread Interactions with Regular File Operations
All of the functions chmod( ), close( ), fchmod( ), fcntl( ), fstat( ),
ftruncate( ), lseek( ), open( ), read( ), readlink( ), stat( ), symlink( ),
and write( ) shall be atomic with respect to each other in the effects
specified in IEEE Std 1003.1-2001 when they operate on regular files. If two
threads each call one of these functions, each call shall either see all of
the specified effects of the other call, or none of them.
Waarschijnlijk is het niet wenselijk om dat altijd strict te volgen, maar toch. Dit gaat overigens specifiek over files, niet sockets of pipes, maar dat is ook de situatie waarin het probleem optreedt.

[ Voor 8% gewijzigd door Soultaker op 28-08-2008 16:41 ]


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Wat zijn die specified effects dan? (ik weet het niet hoor; je kan prima gelijk hebben... Ik vraag het me gewoon af.)

  • wacco
  • Registratie: Augustus 2002
  • Laatst online: 21-03-2023

wacco

cli, hlt.

Topicstarter
Soultaker schreef op donderdag 28 augustus 2008 @ 16:19:*snip*
Verder is FastCGI juist ontwikkeld om met een hoge mate van parallelliteit efficient requests te kunnen afhandelen, dus ik neem aan dat het scenario van meerdere concurrent writes niet nieuw voor ze is. Ik zou dus denken dat als locking nodig is om de correcte werking van de library te garanderen, dat wel geïmplementeerd is. Ongeacht of er nu wel of niet gelockt wordt, zou ik er dus vanuit gaan dat als je de FastCGI library correct gebruikt, de library eventuele locking verder afhandelt.
Dat dacht ik ook, maar ik neem niet graag dingen zomaar aan. Zeker niet als blijkt dat het in sommige gevallen niet lijkt te werken. :P

Gelukkig was iemand op IRC (moshe, haai :w) fanatiek genoeg om de linux kernel eropna te graven, en die kwam met dit op de proppen:
  • write() vertaalt naar sys_write in fs/read_write.c
  • die called vfs_write
  • in het geval van een socket is deze virtueel, dus wordt er do_sync_write aangeroepen
  • die roept aan aio_write, wat een functionpointer is naar sock_aio_write in het geval van sockets, en deze staat in net/socket.c
  • die maakt een message en roept aan do_sock_write
  • hier wordt de message wat ingevult en wordt __sock_sendmsg aangeroepen
  • deze roept sendmsg aan, wat een functionpointer is naar tcp_sendmsg (geset in proto_ops, net/ipv4/af_inet.c) welke staat in net/ipv4/tcp.c
  • eerste wat hier gebeurd is lock_sock, dus een lock voordat de message daadwerkelijk naar de socket file descriptor wordt geschreven :z
Dus het gaat in linux, met sockets, iig wel correct. Het eerdere voorbeeld is dus echt alleen een probleem als de output naar een bestand is. :)

Spolap: Interactive webcomic

Pagina: 1