пятница, 22 сентября 2017 г.

C++: Причуды препроцессора

Кажется, мне удалось найти отличный вопрос для собеседований IT-специалистов :) Значительно лучший, чем про крышки канализационных колодцев. :)

Так вот. Есть один вопрос, который с высокой вероятностью опрокинет навзничь любого сишника или крестоёба :)

Вопрос такой. :)

Будет ли работать в препроцессоре условие с двойным отрицанием и условием OR? Вот такое:

 #if !defined AAAA || !defined BBBB  
 #error We're here!  
 #endif
   

если не определены ни AAAA ни BBBB ? Иначе говоря, зажжется ли error при таких условиях?

Чур, к компилятору не подходить! (не давайте им компилятор на собеседовании!)

Пока вы думаете, посмотрите на условие, которое точно работает (взято из реального кода):

 #if defined __x86_64__ || defined __i386__  
 #include <cstdio>     /* For std::sprintf */  
 #endif  
   

Это работает. :) Более того, оно работает и с условием || и с условием &&. В зависимости от требуемой вам логики, разумеется.

А сейчас - внимание, правильный ответ по предыдущему вопросу. Ответ - нет, истинная ветвь не сработает ни при каких обстоятельствах.

Не спешите гуглить и ссылаться на единственный неправильный ответ по данному примеру на StackOverflow. :) Ответа на этот вопрос в руководствах нет, а на SO, как я и сказал, он неправильный. Не работает.

Правильный ответ на вопрос таков. Чтобы работало, должно быть вот так:

 #if !defined AAAA && !defined BBBB  
 #error We're here!  
 #endif  
   
   

Что означает, что в препроцессоре условие "NOT defined A || NOT defined B" невозможно. Причем необходимая логика достигается как раз-таки условием &&.

Это абсолютно неочевидно, более того, это еще и не документировано. Во всяком случае, для GCC. И не надо кивать на сам язык, в котором if ведет себя нормальным образом - то есть как напишешь, так и будет. Мы говорим о препроцессоре.

А теперь возьмите компилятор и проверьте то, что я сказал. :)

PS. Отличный вопрос, чтобы завалить любого сеньора, проходящего собеседование. 99,9% программистов ответят на вопрос неправильно. Даже если будут гуглить и ползать по SO. :)

четверг, 24 августа 2017 г.

Oracle Clusterware active/passive failover скрипт: усовершенствования

Конфигурация Clusterware active/passive достаточно распространена, несмотря на то, что является костылем (в отличие от полного RAC).

По интернету гуляет перепечатка первоначального варианта скрипта (single_instance_action_script.sh).  Какой-то рукосуй написал как попало на своем Линуксе и теперь этот вариант выдается всеми без исключения индусами - "знатоками Оракла" - за универсальный:

 #!/bin/sh  
   
 export ORA_CRS_HOME=$_CRS_ORA_CRS_HOME  
 export ORACLE_HOME=$_CRS_ORACLE_HOME  
 export ORACLE_SID=$_CRS_ORACLE_SID  
 export SPFILE=$_CRS_SPFILE  
 export LD_LIBRARY_PATH=$ORACLE_HOME/lib:$LD_LIBRARY_PATH  
 NODE_NAME=$(${ORA_CRS_HOME}/bin/olsnodes -l)  
 VIP_IP=$(${ORA_CRS_HOME}/bin/srvctl config vip -n $NODE_NAME | grep IPv4| awk '{print $4}')  
 check_dbstatus() {  
 OUTPUT=`$ORACLE_HOME/bin/sqlplus -s /nolog<<EOF  
 connect / as sysdba  
 set echo off define off heading off pagesize 0  
 SET LINESIZE 100  
 COLUMN l_output FORMAT A100  
 SELECT i.status || ' ' || pa.value || '/' || 'ora_' || p.spid || '.aud' AS  
 l_output  
 FROM v\\$session s,  
 v\\$process p,  
 v\\$parameter pa,  
 v\\$instance i  
 WHERE pa.name = 'audit_file_dest'  
 AND s.paddr = p.addr  
 AND s.sid = (select sid from v\\$mystat where rownum=1) and s.audsid =  
 sys_context('userenv','sessionid');  
 exit  
 EOF`  
   
 DBSTATUS=$(echo $OUTPUT | awk '{print $1}')  
 AUDITFILE=$(echo $OUTPUT | awk '{print $2}')  
 rm -f $AUDITFILE 2>/dev/null  
   
  if [ "$DBSTATUS" == "OPEN" ]  
  then  
   return 0  
  else  
   return 1  
  fi  
 }  
   
 case $1 in  
   
 'start')  
 echo "spfile='${SPFILE}'" > /tmp/init${ORACLE_SID}.ora  
 $ORACLE_HOME/bin/sqlplus /nolog <<EOF  
 connect / as sysdba  
 startup pfile=/tmp/init${ORACLE_SID}.ora  
 alter system set local_listener='(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=${VIP_IP})(PORT=1521))))' scope=memory;  
 alter system set remote_listener='s2-scan:1521' scope=memory;  
 EOF  
 check_dbstatus  
 RET=$?  
 ;;  
   
 'stop')  
 $ORACLE_HOME/bin/sqlplus /nolog <<EOF  
 connect / as sysdba  
 shutdown immediate  
 EOF  
 NUM=`ps -ef | grep -i smon_${ORACLE_SID} | grep -v grep | wc -l`  
 if [ $NUM = 0 ]; then  
  RET=0  
 else  
  RET=1  
 fi  
 ;;  
   
 'clean')  
 $ORACLE_HOME/bin/sqlplus /nolog <<EOF  
 connect / as sysdba  
 shutdown abort  
 EOF  
 ##for i in `ps -ef | grep -i mon_${ORACLE_SID} | awk '{print $2}' ` ;do kill -9 $i; done  
 NUM=`ps -ef | grep -i smon_${ORACLE_SID} | grep -v grep | wc -l`  
 if [ $NUM = 0 ]; then  
  RET=0  
 else  
  RET=1  
 fi  
   
 ;;  
   
 'check')  
 check_dbstatus  
 RET=$?  
 ;;  
   
 '*')  
 RET=0  
 ;;  
 esac  
   
 if [ $RET -eq 0 ]; then  
  exit 0  
 else  
  exit 1  
 fi  
   

При этом скрипт является мало того, что предельно примитивным, он еще и полон башизмов (несмотря на то, что начинается с #!/bin/sh, что подразумевает Борн на большинстве *NIX систем). Ну да, вы можете написать его на перле/питоне/чем угодно сами. Но не будете, верно? Нынешние IT-специалисты не пишут скриптов, и тем не менее катят за "гуру".

Три вещи, которые представляются неправильными в данном скрипте:
  1. Он должен быть переписан под Борн. То, что работает в борне - работает везде.
  2. Скрипт должен поддерживать standby - базы. Кластерный failover - не полноценная защита.
  3. Скрипт должен поддерживать Active Data Guard. Когда standby-база открыта в read only (благодарность Vladislav Frants за наводку).
Не говорю, что предлагаемый вариант идеален. Однако он соответствует всем трем пунктам. Прошу взглянуть:

 #!/bin/sh  
 # Modified to support CW standby's by Yuri Voinov (c) 2017  
 # Version 2.0  
 #  
 # 22.08.2017 - Added ADG support  
 # 16.08.2017 - Initial version  
   
 # Variables  
 ORA_CRS_HOME=$_CRS_ORA_CRS_HOME  
 export ORA_CRS_HOME  
 ORACLE_HOME=$_CRS_ORACLE_HOME  
 export ORACLE_HOME  
 ORACLE_SID=$_CRS_ORACLE_SID  
 export ORACLE_SID  
 SPFILE=$_CRS_SPFILE  
 export SPFILE  
 LD_LIBRARY_PATH=$ORACLE_HOME/lib:$LD_LIBRARY_PATH  
 export LD_LIBRARY_PATH  
 # Workaround for Solaris 10 branded zones on SuperCluster  
 if [ "`uname -s`" = "SunOS" -a "`uname -r`" = "5.10" ]; then  
  LD_LIBRARY_PATH_64=""  
  export LD_LIBRARY_PATH_64  
 fi  
 NODE_NAME="`${ORA_CRS_HOME}/bin/olsnodes -l`"  
 VIP_IP="`${ORA_CRS_HOME}/bin/srvctl config vip -n $NODE_NAME | grep IPv4| awk '{print $4}'`"  
   
 # Subroutines  
 check_dbstatus() {  
 OUTPUT="`$ORACLE_HOME/bin/sqlplus -s /nolog<<EOF  
 connect / as sysdba  
 set echo off define off heading off pagesize 0  
 SET LINESIZE 100  
 COLUMN l_output FORMAT A100  
 SELECT i.status || ' ' || pa.value || '/' || 'ora_' || p.spid || '.aud' AS  
 l_output  
 FROM v\\$session s,  
 v\\$process p,  
 v\\$parameter pa,  
 v\\$instance i  
 WHERE pa.name = 'audit_file_dest'  
 AND s.paddr = p.addr  
 AND s.sid = (select sid from v\\$mystat where rownum=1) and s.audsid =  
 sys_context('userenv','sessionid');  
 exit  
 EOF`"  
   
 # Check DB role  
 OUTPUT2="`$ORACLE_HOME/bin/sqlplus -s /nolog<<EOF  
 connect / as sysdba  
 set echo off define off heading off pagesize 0  
 set linesize 100  
 select database_role from v\\$database;  
 exit  
 EOF`"  
   
 DBSTATUS="`echo $OUTPUT | awk '{print $1}'`"  
 DBSTATUS2="`echo $OUTPUT2 | awk '{print $1 $2}'`"  
 AUDITFILE="`echo $OUTPUT | awk '{print $2}'`"  
 rm -f $AUDITFILE 2>/dev/null  
   
 # ADG support: PHYSICAL STANDBY can be opened as READ ONLY.  
 if [ "$DBSTATUS2" = "PHYSICALSTANDBY" -a \( "$DBSTATUS" = "MOUNTED" -o "$DBSTATUS" = "OPEN" \) ]; then  
  exit 0  
 elif [ "$DBSTATUS2" = "PRIMARY" -a "$DBSTATUS" = "OPEN" ]; then  
  exit 0  
 else  
  exit 1  
 fi  
 }  
   
 case "$1" in  
   
 "start")  
 echo "spfile=$SPFILE" > /tmp/init$ORACLE_SID.ora  
 $ORACLE_HOME/bin/sqlplus -s /nolog<<EOF  
 connect / as sysdba  
 startup mount pfile=/tmp/init${ORACLE_SID}.ora  
 alter system set local_listener='(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=${VIP_IP})(PORT=1521))))' scope=memory;  
 alter system set remote_listener='s2-scan:1521' scope=memory;  
 exit  
 EOF  
 OUTPUT3="`$ORACLE_HOME/bin/sqlplus -s /nolog<<EOF  
 connect / as sysdba  
 set echo off define off heading off pagesize 0  
 set linesize 100  
 select database_role from v\\$database;  
 exit  
 EOF`"  
   
 DBSTATUS3="`echo $OUTPUT3 | awk '{print $1}'`"  
   
 if [ "$DBSTATUS3" = "PRIMARY" ]; then  
 $ORACLE_HOME/bin/sqlplus -s /nolog<<EOF  
 connect / as sysdba  
 alter database open;  
 exit  
 EOF  
 fi  
 check_dbstatus  
 ;;  
   
 "stop")  
 $ORACLE_HOME/bin/sqlplus -s /nolog <<EOF  
 connect / as sysdba  
 shutdown immediate  
 exit  
 EOF  
   
 if [ -n "`ps -ef | grep -i smon_${ORACLE_SID} | grep -v grep | wc -l`" ]; then  
  exit 0  
 else  
  exit 1  
 fi  
 ;;  
   
 "clean")  
 $ORACLE_HOME/bin/sqlplus -s /nolog<<EOF  
 connect / as sysdba  
 shutdown abort  
 exit  
 EOF  
   
 if [ -n "`ps -ef | grep -i smon_${ORACLE_SID} | grep -v grep | wc -l`" ]; then  
  exit 0  
 else  
  exit 1  
 fi  
 ;;  
   
 "check")  
 check_dbstatus  
 ;;  
   
 *)  
 exit 0  
   
 esac  
   

Это Борн, насколько мне вообще удалось его сделать Борн-совместимым, поддерживается Standby, поддерживается ADG. Кроме того, включен один workaround (для issue. у которой нет удовлетворительного объяснения и, тем более, документированного решения на MOS).

По данному workaround я просто покажу итоговые данные и логи:

 root @ s1-host1 /ora/grid # /ora/grid.12.1.0.2/bin/crsctl start res namedb.db  
 CRS-2672: Attempting to start 'namedb.db' on 's1-host1'  
 CRS-2674: Start of 'namedb.db' on 's1-host1' failed  
 CRS-2679: Attempting to clean 'namedb.db' on 's1-host1'  
 CRS-2681: Clean of 'namedb.db' on 's1-host1' succeeded  
 CRS-2563: Attempt to start resource 'namedb.db' on 's1-host1' has failed. Will re-retry on 's1-host12' now.  
 CRS-2672: Attempting to start 'namedb.db' on 's1-host12'  
 CRS-2674: Start of 'namedb.db' on 's1-host12' failed  
 CRS-2679: Attempting to clean 'namedb.db' on 's1-host12'  
 CRS-2681: Clean of 'namedb.db' on 's1-host12' succeeded  
 CRS-2632: There are no more servers to try to place resource 'namedb.db' on that would satisfy its placement policy  
 CRS-4000: Command Start failed, or completed with errors.  
 root @ s1-host1 /ora/grid # /ora/grid.12.1.0.2/bin/crsctl status resource namedb.db  
 NAME=namedb.db  
 TYPE=single_instance_db  
 TARGET=ONLINE  
 STATE=OFFLINE  
   
 root @ s1-host1 /ora/grid # /ora/grid.12.1.0.2/bin/crsctl stop res namedb.db  
 root @ s1-host1 /ora/grid # /ora/grid.12.1.0.2/bin/crsctl status resource namedb.db  
 NAME=namedb.db  
 TYPE=single_instance_db  
 TARGET=OFFLINE  
 STATE=OFFLINE  
   
   
 root @ s1-host1 / # cat /tmp/script.log  
 ORA_CRS_HOME=/ora/grid.12.1.0.2  
 + export ORA_CRS_HOME   
 ORACLE_HOME=/ora/oracle/10.2.0.4.11  
 + export ORACLE_HOME   
 ORACLE_SID=namedb  
 + export ORACLE_SID   
 SPFILE=/s1_namedb_data/namedb/spfilenamedb.ora  
 + export SPFILE   
 LD_LIBRARY_PATH=/ora/oracle/10.2.0.4.11/lib:/ora/grid.12.1.0.2/lib:/opt/ORCLcluster/lib:/usr/lib:/usr/ucblib:/ora/grid.12.1.0.2/lib:/opt/ORCLcluster/lib:/usr/lib:/usr/ucblib:/ora/grid.12.1.0.2/lib:/opt/ORCLcluster/lib:/usr/lib:/usr/ucblib:/ora/grid.12.1.0.2/lib:/opt/ORCLcluster/lib:/usr/lib:/usr/ucblib:/ora/grid.12.1.0.2/lib:/opt/ORCLcluster/lib:/usr/lib:/usr/ucblib:  
 + export LD_LIBRARY_PATH   
 + /ora/grid.12.1.0.2/bin/olsnodes -l   
 NODE_NAME=s1-host1  
 + /ora/grid.12.1.0.2/bin/srvctl config vip -n s1-host1   
 + awk {print $4}   
 + grep IPv4   
 VIP_IP=192.168.180.91  
 + check_dbstatus   
 + /ora/oracle/10.2.0.4.11/bin/sqlplus -s /nolog   
 connect / as sysdba  
 set echo off define off heading off pagesize 0  
 SET LINESIZE 100  
 COLUMN l_output FORMAT A100  
 SELECT i.status || ' ' || pa.value || '/' || 'ora_' || p.spid || '.aud' AS  
 l_output  
 FROM v$session s,  
 v$process p,  
 v$parameter pa,  
 v$instance i  
 WHERE pa.name = 'audit_file_dest'  
 AND s.paddr = p.addr  
 AND s.sid = (select sid from v$mystat where rownum=1) and s.audsid =  
 sys_context('userenv','sessionid');  
 exit  
 ld.so.1: sqlplus: fatal: relocation error: file /ora/oracle/10.2.0.4.11/lib/libnnz10.so: symbol nzdacvalue: referenced symbol not found  
 OUTPUT=  
 + /ora/oracle/10.2.0.4.11/bin/sqlplus -s /nolog   
 connect / as sysdba  
 set echo off define off heading off pagesize 0  
 set linesize 100  
 select database_role from v$database;  
 exit  
 ld.so.1: sqlplus: fatal: relocation error: file /ora/oracle/10.2.0.4.11/lib/libnnz10.so: symbol nzdacvalue: referenced symbol not found  
 OUTPUT2=  
 + awk {print $1}   
 + echo   
 DBSTATUS=  
 + awk {print $1 $2}   
 + echo   
 DBSTATUS2=  
 + awk {print $2}   
 + echo   
 AUDITFILE=  
 + rm -f   
 + [ = PHYSICALSTANDBY -a = MOUNTED ]   
 + [ = PRIMARY -a = OPEN ]   
 + exit 1   
   
 #!/bin/sh  
 # Put at the beginning on script. Born/bash compatible  
 set -x  
 exec 2>/tmp/script.log  
   

Обратите внимание на последние строчки. Именно это решение позволило идентифицировать, локализовать и решить проблему. Это просто редирект всего выполнения скрипта, вместе с stdout/stderr в режиме отладки в лог. В данной ситуации увидеть, почему не выполняется, по-другому просто невозможно.

суббота, 22 июля 2017 г.

Solaris 11: Настройка DNS вручную

Я уже говорил, что Солярис 11 - это ад и Пакистан? Изначально отличная система общего назначения (имею в виду Солярис 10, разумеется) попала в шелудивые ручонки пидораиндусов-линукоидов и превратилась - та-дааааааааа! - в долбаный Линукс! Во всяком случае, на уровне юзерлэнда (убунтовская бета libtool в production(!) Solaris 11.3 - желающие могут убедиться самостоятельно) и некоторых ключевых аспектов администрирования.

Да, я считаю, что пидороразработчиков 11 надо линчевать. В балахонах, с факелами и вилами.

Нарушены как минимум два важнейших принципа - "Совместимость важнее производительности" и "Keep It Simple, Stupid".

Рассмотрим такую простую вещь, как настройка DNS-резолва после установки системы. Ну или вам его надо изменить. Быстро.

Как мы это делали в десятке? Всегда правой:
 # cp /etc/nsswitch.dns /etc/nsswitch.conf  
 # vi /etc/resolv.conf  
 # svcadm restart dns/client  

Не просто - а очень просто, руки до сих пор помнят. Делается на полном автомате, без единой заминки.

Оба-на, в Солярис 11 это не канает! Вы больше не можете редактировать настройки resolver ручками, так как они - сюрприз, мать вашу! - задаются при старте сервисами SMF (точнее, свойствами ДВУХ сервисов) и редактировать текстовые конфиги без толку - они будут возвращены в исходное состояние при рестарте оных сервисов/системы!

Индусы, чтоб вы сдохли, суки! Это до какой степени можно было обдолбаться и насрать на UNIX-way и два основополагающих принципа!

Смотрим, как это делается ТЕПЕРЬ:

 svccfg -s dns/client  
 svc:/network/dns/client> listprop config  
 ...  
 svc:/network/dns/client> setprop config/nameserver = (127.0.0.1)  
 svc:/network/dns/client> setprop config/domain = localdomain  
 svc:/network/dns/client> listprop config  
 config application  
 config/value_authorization astring solaris.smf.value.name-service.dns.client  
 config/nameserver net_address 127.0.0.1  
 config/domain astring localdomain  
 svc:/network/dns/client> exit  
 # svcadm refresh dns/client;svcadm restart dns/client  
   
 svccfg -s name-service/switch  
 svc:/system/name-service/switch> setprop config/host = “files dns”  
 svc:/system/name-service/switch> setprop config/ipnode = astring: “files dns”  
 svc:/system/name-service/switch> exit  
 # svcadm refresh name-service/switch;svcadm restart name-service/switch  
   

ПидораДолбодятлы, вы совсем охуеохренели? Вместо трех простых команд последовательность действий, которую без стакана и не вспомнишь!

Я хочу сказать, что качество системы провалилось в немытую индусскую задницу. Такое, с позволения сказать, администрирование ни в какие ворота не лезет.

Я понимаю, что для этого были какие-то мудацкие соображения. Но все на свете администраторы Солярис прокляли пидороразработчиков Индии до 149 колена. Шлю персонально лучи поноса.

суббота, 1 июля 2017 г.

Telegram: обход блокировок в один клик?

Посмотрел я решение для мобильного Telegram, предложенное командой Telegram "для обхода блокировок в один клик", как было анонсировано Павлом.

Я понимаю, что времени было ровно настолько, чтобы сделать "на отъебись" просто приблуду с соксификацией, как в десктопной версии. Однако, Павел, это, пардон муа, несерьезно.

Первое. Заблокировать Телеграм не просто, а очень просто. Я вот здесь уже писал год назад, насколько это легко. Это не просто - это тривиально. 

Процедура бутстрап у Телеграма элементарная и выбивается на раз. Две сети в блок-лист + блокирование всего, лишь отдаленно напоминающего SOCKS. По NBAR или просто блокированием портов. Вуаля - большинство пользователей никогда не войдут.

Соксификация? Ха! Я просто зарублю порт 1080 на Микротике/DLink или любом другом барахле! Большинство обычных людей в жизни не догадается, что соксы бывают на другом порту, а если и услышат от знакомого сисадмина - палец о палец не ударят чтобы их найти.

Второе. Люди, обитающие в башнях из слоновой кости, как-то забывают, что с публичными SOCKS-прокси свыше 10 лет ведется масштабная непримиримая война всеми на свете. Списки автоматически мониторятся и сервера попадают в бан максимум через 15 минут после своего появления на свет. Всеми. Начиная с провайдеров - большинство это делает - и заканчивая любым мало-мальски опытным сисадмином. По очевидной причине. Это - рассадник.

Обычный смертный же, получив обновление 1.1.9, должен сперва ручками найти IP-адрес незаблокированного SOCKS-прокси, ручками его вставить в настройки Телеграма. И, скрестив пальцы, надеяться на лучшее. Желательно при этом понимать, что в топе гугла работающих SOCKS-прокси найти так же реально, как обнаружить алмаз "Шах" на Красной Площади в разгар народных гуляний. А также то, что пользователю неслабо бы иметь определенную экспертизу - от повышенной до очень повышенной - в IT.

Вы когда-нибудь пробовали (на смарте) найти незаблокированный SOCKS-прокси? Рекомендую. Да хотя бы на десктопе.

Я искренне удивлен тем, что человек из IT мог предложить такое в качестве решения для всех.

Из моего личного опыта. Крайне трудно заблокировать лишь Телеграм, оборудованный бриджированным (и обфусцированным!) Тором. Во всяком случае мне неизвестны DPI, способные за разумные средства побанить бриджированный (и обфусцированный!) Тор. Исключая, может быть, Golden Shit - и то я в этом сомневаюсь.

Однако.

Вы никогда не пробовали, даже не будучи хомячком, развернуть бриджированный Тор на смарте, который не рутован? Рекомендую. Великолепный квест.

На десктопе? Да, можно. Имея экспертизу. И ценой определенных, довольно приличных, телодвижений. Я описал в предыдущей статье. Но это никак не один, не два, и даже не двадцать кликов.

Павел, ты точно уверен, что обычный человек способен это проделать? И захочет это проделывать? Не имея экспертизы и не настолько любя котиков?

Я ожидал, что в новой версии будет как минимум встроен Тор - с блэкджеком бриджами и шлюхами обфускацией. И одной-единственной кнопочкой - "Послать цензора на.....".

Однако - не увидел. Все понимаю, Павел. Однако - я разочарован.

Начинайте писать новый мессенджер с нормальной процедурой бутстрапа и встроенной антицензурой. Старому уже никакой SOCKS не поможет. Джузеппе Гарибальди покажет "Окей" из могилы. Если удастся это провернуть.

суббота, 24 июня 2017 г.

Использование Tor Expert Bundle на localhost

Использование Tor Expert Bundle не совсем тривиально, ввиду отсутствия внятной документации а также отсутствия в составе пакета некоторых необходимых компонентов.

Я хочу сразу оговориться, что речь идет о применении Tor не в оппозиционных и иных целях. Мне приходилось сталкиваться с ситуацией, когда в провайдерских сетях отсутствует внутрисетевой роутинг (по различным причинам), и в принципе необходимо попасть в соседнюю сеть (например, с целью администрирования собственных серверов), однако, ввиду повсеместного (и, зачастую, оправданного) резко отрицательного отношения к открытым прокси, это крайне затруднительно.

Дисклеймер. Да, данный инструмент может использоваться в различных целях, как и кухонный нож. Вы принимаете решение о его применении на свой страх и риск и на собственную ответственность.

Итак, исходные данные. 

У нас имеется станция под управлением Windows и нам нужен сервис Tor, с точкой входа в виде SOCKS proxy на локальной машине для торификации некоторых программ с доступом в Интернет. Причем, по-возможности, не зависящий от произвола провайдеров и государственных служб (я предупреждал, господа).

Для решения данной задачи нам понадобится Tor Expert Bundle, а также Pluggable Transports, не входящие в состав пакета.

Вам, в ряде случаев, придется озаботиться тем, как попасть на https://torproject.org и получить оттуда два важных компонента: собственно Tor Expert Bundle и Tor Browser (он нужен, поскольку в его состав входят Pluggable Transports).

Сперва необходимо установить Тор браузер. По умолчанию он ставится в Desktop:


Обычно я просто собираю эту директорию в архив и сохраняю вместе с Tor Expert Bundle.

Здесь обычно задают вопрос - "А на кой мне возиться с Expert Bundle, если у меня уже есть работащий торифицированны браузер?" На той, что не только браузер может нуждаться в торифицированном соединении. А, например, SSH-клиент. Мессенджеры. И так далее. Да и, зачастую, удобнее иметь постоянно работающий сервис, нежели зависеть от браузера, который надо держать запущенным.

Сам Tor Expert Bundle нужно распаковать в Program Files, например в "C:\Program Files (x86)\tor" и туда же распаковать Pluggable Transports:



Самое сложное в использовании Expert Bundle - это конфигурирование. Сначала нужно создать сервис, это просто:


Обратите внимание на замечание. Сервис выполняется из-под локального системного аккаунта. Что означает, что конфигурационные файлы и данные находятся здесь (обратите внимание, torrc отсутствует и сервис запущен с умолчаниями):

Это не то, что нам нужно, к тому же, нас не удовлетворяет запуск сервиса от системного аккаунта (это достаточно небезопасно, к тому же, значительно сложнее сделать правильную конфигурацию). Поэтому запустим управление компьютером и поменяем аккаунт сервиса на пользовательский:

Окей, теперь у нас рабочая директория находится у пользователя. Если по указанному пути ее нет, создадим ее:

Теперь в нее можно положить torrc, который надо написать. Взяв за основу torrc браузера, напишем свой конфигурационный файл:

 ####  
 # cd C:\Users\Yuri\AppData\Roaming  
 # mkdir tor  
 SocksPort 9050 IPv6Traffic PreferIPv6 KeepAliveIsolateSOCKSAuth  
   
 Bridge 198.147.22.36:443 58DB653508197599B8CB7EE95772F3EF2255939D  
 Bridge 194.132.209.138:22670 582E2CD0132AE1FD89426EC624B76B36690A622F  
 Bridge 194.132.209.116:20650 0D113A4B44E9B79C604DFC8DAE35C3C74CF60FF9  
   
 Bridge obfs4 194.132.209.138:48574 582E2CD0132AE1FD89426EC624B76B36690A622F cert=G1jt3rntMnoWTy8uEeQu9VPhnmPn7BH5cHwXbSkQDNlyrFD6BL7D8PMiPwPooKGudt1iYw iat-mode=0  
 Bridge obfs4 194.132.209.116:34596 0D113A4B44E9B79C604DFC8DAE35C3C74CF60FF9 cert=QfRegE3lzpqsnWUAQTulUPje7KgdcL2c+qOUtoU0Jw/ln8KpPZbh43XdfKr1kXYYLLcYQg iat-mode=0  
 Bridge obfs4 68.45.52.117:40365 3C89FB56CDEE23F0F16FDF86086866E33EAB24D8 cert=s0SmVQop+pZPZxlHunrXQL6MW4uVOZS55XjDVaBYkaSSoN9FEZOif/dxxrufg6ZnskRkSw iat-mode=0  
   
 Bridge obfs3 194.132.209.138:54673 582E2CD0132AE1FD89426EC624B76B36690A622F  
 Bridge obfs3 194.132.209.116:44364 0D113A4B44E9B79C604DFC8DAE35C3C74CF60FF9  
 Bridge obfs3 68.45.52.117:36125 3C89FB56CDEE23F0F16FDF86086866E33EAB24D8  
   
 GeoIPFile C:\Program Files (x86)\Tor\Data\Tor\geoip  
 GeoIPv6File C:\Program Files (x86)\Tor\Data\Tor\geoip6  
 HiddenServiceStatistics 0  
 UseBridges 1  
   
 ClientTransportPlugin obfs2,obfs3,obfs4,scramblesuit exec C:\Program Files (x86)\Tor\PluggableTransports\obfs4proxy  
   

Бриджи предварительно получим на https://bridges.torproject.org. Я обычно добавляю обычные бриджи для облегчения бутстрапа, но это может вызвать некоторые проблемы - обычные бриджи иногда банятся. Поэтому стоит добавить обфусцированные, запустив соответствующий транспортный плагин (внизу в конфигурации). Обратите внимание на полные пути (в кавычки не берем!). Чтобы не терять соединения в случае активного бана бриджей (а также ввиду того, что иногда они меняются или перестают работать) советую за несколько последующих дней собрать десяток-два бриджей в каждой группе. Обязательно укажите SOCKS-порт, как показано - он будет точкой входа, и, кстати, IPv6 будет полезен даже если в вашей стране IPv6 не применяется - изредка это облегчает бутстраппинг и последующую работу Тор. 

Если применению Тор активно противодействуют, откажитесь от использования необфусцированных бриджей.

Я не использую FTE, так как плагин достаточно капризный и в последней версии Tor Bundle не работает из-за ошибки (исправят - начну использовать снова). В принципе, OBFS3/4 вполне достаточно.

Убедитесь, что конфигурация работает, запустив Тор интерактивно (предварительно остановив сервис, разумеется) - что бутстраппинг проходит до конца и клиентское соединение устанавливается.

Убедившись, что все в порядке и Тор в состоянии соединиться с сетью, запустите сервис.

Обратите внимание вот на что. В Тор есть небольшой баг, касающийся остановки Pluggble Transport при остановке его самого. Ввиду этого может понадобиться руками убить выполнение obfs4proxy в диспетчере задач (и, в общем, это может понадобиться делать не раз при перезапусках):

Теперь, когда сервис работает, можно настроить, например, Телеграм на его использование:


Аналогично можно настроить, например, Mail.ru Agent (хотя я считаю глупостью небезопасный по определению мессенджер прятать в туннель), или SSH client:


Проверяем:

Порядок, соединяемся через Tor. Консоль даже не слишком тормозит.

Резюмируем. Вы имеете туннельный сервис с SOCKS5 на входе. Любой браузер, большинство мессенджеров - словом, все программы, могущие использовать SOCKS в соединениях - могут быть выпущены через туннель.

Пользуйтесь на здоровье. И не забывайте время от времени обновлять/пополнять бриджи в конфигурации, убирая умершие и накапливая рабочие. Опыт показывает, что, чем больше вы имеете бриджей в конфигурации - тем более бесперебойно работает сервис.

воскресенье, 30 апреля 2017 г.

С++: Fastest check all digits in std::string

По мотивам вот этой переписки самое быстрое из известных решение (С++11, никаких итераторов и других извращений):

 /* Fast check if string contains only digits */  
 static bool DigitsOnly(const std::string &p_str) {  
      for (size_t i = 0, v_len = p_str.length(); i < v_len; ++i) {  
            if ((p_str[i] ^ '0') > 9)  
                return false;  
      }  
      return true;  
 }  
   

Можете проделать бенчмарк, если есть сомнения.

суббота, 15 апреля 2017 г.

C++: Использование std::vector как dynamic array

Я вот здесь уже писал, как лучше организовывать пулы тредов переменной величины.

Так вот. Найдено наилучшее и наиболее элегантное решение. :)

      v_threads.resize(v_max_threads);  
      /*-------------Main Loop----------------*/  
      while (!std::cin.eof()) {  
           /* Start processing threads */  
           for (auto &t : v_threads) {  
                t = std::thread([]() { processdata(); });  
           }  
           /* Finish all threads */  
           for (auto &t : v_threads) {  
                t.join();  
           }  
      }  
      /*-------------Main Loop----------------*/  
   

Никаких push/emplace_back, никаких clear(). Вектор используется как array, причем с лучшими чертами динамических массивов и std::array.

Адресация элементов уже инициализированного вектора (resize вместо reserve) работает быстрее, чем push_back (примерно в четыре раза), и, хотя resize() и медленнее, чем reserve(), но она находится вне цикла и выполняется только один раз.

пятница, 14 апреля 2017 г.

Solaris: диагностика lock contention


Все знают одну из самых известных проблем с производительностью, а, точнее, с масштабированием. Это конкуренция блокировок (lock contention).

На первый взгляд кажется, что многопоточные приложения должны масштабироваться линейно. Так считают зачастую даже весьма опытные администраторы и даже (!) разработчики.

Как бы не так.

Невозможно всегда и везде писать lock-free код. Атомарные операции предельно ограничены областями, в которых их вообще можно использовать.

Стоит, например, заговорить об обработке строк - и привет, строковые операции невозможно выполнять атомарно по определению, они не допускают тривиального копирования, будучи контейнерами.

Наиболее часто в современном коде используются мьютексы, наиболее часто они эксклюзивные. Если говорить о средах разработки Солярис, то штатные библиотеки так густо усыпаны мютексами для гарантирования потокобезопасности, что непонятно, как код вообще может исполняться параллельно (мы же понимаем, что каждая блокировка мьютекса - это точка сериализации, да?). 

Вместе с тем, стоит отметить, что от среды разработки этот эффект, в общем, зависим мало - можно и с использованием GCC/STL понаписать такого, что куча процессорных ядер будет стоять колом в ожидании снятия блокировки.

Впрочем, речь не об этом.

Поговорим о том, как можно диагностировать lock contention, так как возникновение конкуренции блокировок вообще неочевидно и не всегда легко обнаруживается. 

Это важно, так как чтобы устранить bottleneck, надо его сперва обнаружить.

lockstat и standalone diagnostics

Если у вас есть возможность выделить самостоятельную программу для автономного запуска, вы можете использовать утилиту lockstat для диагностики конкуренции блокировок в различных режимах выполнения вашего кода (ну, или не вашего - главное, иметь возможность его запуска).

Я использовал в примерах одну программу, написанную в соавторстве с Joe Lawand, которая выполняется в асинхронном режиме (тредами) в диапазоне от 1 до 65534 треда на процесс.

Программа содержит три глобальных мьютекса (она оптимизирована, мьютексы эксклюзивные - lock_guard, но чрезвычайно короткие и в основных режимах работы используются лишь два из них):

 std::mutex g_cin_mtx, g_cout_mtx, g_log_mtx;     /* cin/cout/log guards */  

Как я и сказал, мьютексы ограничены очень короткой областью видимости:

      {  
           std::lock_guard<std::mutex> lock(g_cout_mtx);  
           std::cout << v_out;  
      }  
   

и снимаются немедленно по завершении критической секции кода.

Для тестов использовался 8-ядерный сервер с двумя процессорами Xeon под Solaris 10:

 root @ khorne / # psrinfo    
 0    on-line  since 04/12/2017 13:18:20  
 1    on-line  since 04/12/2017 13:18:31  
 2    on-line  since 04/12/2017 13:18:31  
 3    on-line  since 04/12/2017 13:18:31  
 4    on-line  since 04/12/2017 13:18:31  
 5    on-line  since 04/12/2017 13:18:31  
 6    on-line  since 04/12/2017 13:18:31  
 7    on-line  since 04/12/2017 13:18:31  
 root @ khorne / # psrinfo -v  
 Status of virtual processor 0 as of: 04/14/2017 19:40:41  
  on-line since 04/12/2017 13:18:20.  
  The i386 processor operates at 2333 MHz,  
     and has an i387 compatible floating point processor.  
 Status of virtual processor 1 as of: 04/14/2017 19:40:41  
  on-line since 04/12/2017 13:18:31.  
  The i386 processor operates at 2333 MHz,  
     and has an i387 compatible floating point processor.  
 Status of virtual processor 2 as of: 04/14/2017 19:40:41  
  on-line since 04/12/2017 13:18:31.  
  The i386 processor operates at 2333 MHz,  
     and has an i387 compatible floating point processor.  
 Status of virtual processor 3 as of: 04/14/2017 19:40:41  
  on-line since 04/12/2017 13:18:31.  
  The i386 processor operates at 2333 MHz,  
     and has an i387 compatible floating point processor.  
 Status of virtual processor 4 as of: 04/14/2017 19:40:41  
  on-line since 04/12/2017 13:18:31.  
  The i386 processor operates at 2333 MHz,  
     and has an i387 compatible floating point processor.  
 Status of virtual processor 5 as of: 04/14/2017 19:40:41  
  on-line since 04/12/2017 13:18:31.  
  The i386 processor operates at 2333 MHz,  
     and has an i387 compatible floating point processor.  
 Status of virtual processor 6 as of: 04/14/2017 19:40:41  
  on-line since 04/12/2017 13:18:31.  
  The i386 processor operates at 2333 MHz,  
     and has an i387 compatible floating point processor.  
 Status of virtual processor 7 as of: 04/14/2017 19:40:41  
  on-line since 04/12/2017 13:18:31.  
  The i386 processor operates at 2333 MHz,  
     and has an i387 compatible floating point processor.  
   

Значение степени параллелизма по умолчанию у программы равно 4 треда на процесс, и в нормальном состоянии конкуренции блокировок не фиксируется:

 root @ khorne / # lockstat -s 10 -I ./store-id-helper      
  
 Profiling interrupt: 16 events in 0.021 seconds (750 events/sec)  
   
 -------------------------------------------------------------------------------  
 Count indv cuml rcnt   nsec CPU+PIL        Caller           
   2 12% 12% 0.00   1280 cpu[0]         i86_mwait+0xd        
   
    nsec ------ Time Distribution ------ count   Stack            
    2048 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 2     cpu_idle_mwait+0x145    
                            idle+0x89          
                            thread_start+0x8      
 -------------------------------------------------------------------------------  
 Count indv cuml rcnt   nsec CPU+PIL        Caller           
   2 12% 25% 0.00   1899 cpu[7]         i86_mwait+0xd        
   
    nsec ------ Time Distribution ------ count   Stack            
    2048 |@@@@@@@@@@@@@@@        1     cpu_idle_mwait+0x145    
    4096 |@@@@@@@@@@@@@@@        1     idle+0x89          
                            thread_start+0x8      
 -------------------------------------------------------------------------------  
 Count indv cuml rcnt   nsec CPU+PIL        Caller           
   2 12% 38% 0.00   1392 cpu[6]         i86_mwait+0xd        
   
    nsec ------ Time Distribution ------ count   Stack            
    2048 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 2     cpu_idle_mwait+0x145    
                            idle+0x89          
                            thread_start+0x8      
 -------------------------------------------------------------------------------  
 Count indv cuml rcnt   nsec CPU+PIL        Caller           
   2 12% 50% 0.00   1467 cpu[5]         i86_mwait+0xd        
   
    nsec ------ Time Distribution ------ count   Stack            
    2048 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 2     cpu_idle_mwait+0x145    
                            idle+0x89          
                            thread_start+0x8      
 -------------------------------------------------------------------------------  
 Count indv cuml rcnt   nsec CPU+PIL        Caller           
   2 12% 62% 0.00   4039 cpu[3]         i86_mwait+0xd        
   
    nsec ------ Time Distribution ------ count   Stack            
    2048 |@@@@@@@@@@@@@@@        1     cpu_idle_mwait+0x145    
    4096 |                0     idle+0x89          
    8192 |@@@@@@@@@@@@@@@        1     thread_start+0x8      
 -------------------------------------------------------------------------------  
 Count indv cuml rcnt   nsec CPU+PIL        Caller           
   2 12% 75% 0.00   1238 cpu[1]         i86_mwait+0xd        
   
    nsec ------ Time Distribution ------ count   Stack            
    2048 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 2     cpu_idle_mwait+0x145    
                            idle+0x89          
                            thread_start+0x8      
 -------------------------------------------------------------------------------  
 Count indv cuml rcnt   nsec CPU+PIL        Caller           
   2 12% 88% 0.00   949 cpu[2]         i86_mwait+0xd        
   
    nsec ------ Time Distribution ------ count   Stack            
    1024 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 2     cpu_idle_mwait+0x145    
                            idle+0x89          
                            thread_start+0x8      
 -------------------------------------------------------------------------------  
 Count indv cuml rcnt   nsec CPU+PIL        Caller           
   1  6% 94% 0.00   1236 cpu[4]         anon_get_slot+0x82     
   
    nsec ------ Time Distribution ------ count   Stack            
    2048 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 1     anon_array_enter+0x105   
                            segvn_faultpage+0x124    
                            segvn_fault+0x98f      
                            as_fault+0x205       
                            pagefault+0x8b       
                            trap+0x3d7         
                            cmntrap+0x140        
 -------------------------------------------------------------------------------  
 Count indv cuml rcnt   nsec CPU+PIL        Caller           
   1  6% 100% 0.00   2581 cpu[4]         mutex_enter+0x10      
   
    nsec ------ Time Distribution ------ count   Stack            
    4096 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 1     hment_prepare+0x6b     
                            hati_pte_map+0x1e3     
                            hati_load_common+0x12b   
                            hat_memload+0x6a      
                            hat_memload_region+0x9   
                            segvn_faultpage+0x264    
                            segvn_fault+0x98f      
                            as_fault+0x205       
                            pagefault+0x8b       
 -------------------------------------------------------------------------------  

Как видно, ожидания снятия блокировок есть, но очень короткие.

Запустим теперь 1000 тредов на один процесс:

 lockstat -s 10 -I ./store-id-helper<t1.txt -1000  

Взгляните, появились ожидания LWP:


Вот именно то, о чем я говорил - конкуренция за блокировки. Потоки стоят в очереди, ожидая освобождения двух мьютексов.

Более наглядно это можно увидеть на гистограмме:


Значения счетчиков события ожидания выросли. Становится видно, что ожидающий процесс переходит в состояние idle и планировщик снимает его с выполнения.

plockstat и runtime diagnostics


Предыдущий пример показывает, как использовать standalone diagnostics. Чаще всего, однако, нам необходима диагностика на живых системах, в режиме runtine execution.

Для этого используем plockstat.

Данная утилита позволяет увидеть lock contention еще более наглядно. Чтобы довести ситуацию до абсурда, выполним два прогона: один в дефолтной конфигурации (4 треда), и один - с запуском максимального возможного количества, по верхней границе лимита программы:

 /* No lock contention - 4 threads */  
   
 root @ khorne / # plockstat -C -n 1000 -s 1 -e 1 -p 4295   
     0  
 root @ khorne / #   
   
   
   
 /* Lock contention - 65534 threads */  
   
   root 17765 16422  2 18:46:14 pts/2    0:05 store-id-helper -65534  
 root @ khorne / # plockstat -C -n 1000 -s 1 -e 1 -p 17765  
     0  
 Mutex block  
   
 -------------------------------------------------------------------------------  
 Count   nsec Lock             Caller  
   22 204393378 libc.so.1`__sbrk_lock    0  
   
    nsec ---- Time Distribution --- count Stack  
    8192 |@@           |   2 0  
    16384 |@            |   1   
    32768 |            |   0   
    65536 |@            |   1   
   131072 |@@           |   2   
   262144 |            |   0   
   524288 |            |   0   
   1048576 |            |   0   
   2097152 |            |   0   
   4194304 |            |   0   
   8388608 |            |   0   
  16777216 |            |   0   
  33554432 |            |   0   
  67108864 |@@@@@@@@@        |   9   
  134217728 |@            |   1   
  268435456 |@@           |   2   
  536870912 |@@           |   2   
 1073741824 |@@           |   2   
 -------------------------------------------------------------------------------  
 Count   nsec Lock             Caller  
   3 447414272 0x449740           0  
   
    nsec ---- Time Distribution --- count Stack  
    65536 |@@@@@@@@        |   1 0  
   131072 |            |   0   
   262144 |            |   0   
   524288 |            |   0   
   1048576 |            |   0   
   2097152 |            |   0   
   4194304 |            |   0   
   8388608 |            |   0   
  16777216 |            |   0   
  33554432 |            |   0   
  67108864 |            |   0   
  134217728 |            |   0   
  268435456 |@@@@@@@@        |   1   
  536870912 |            |   0   
 1073741824 |@@@@@@@@        |   1   
 -------------------------------------------------------------------------------  
 Count   nsec Lock             Caller  
   3 268457301 0x449980           0  
   
    nsec ---- Time Distribution --- count Stack  
    65536 |@@@@@@@@        |   1 0  
   131072 |            |   0   
   262144 |            |   0   
   524288 |            |   0   
   1048576 |            |   0   
   2097152 |            |   0   
   4194304 |            |   0   
   8388608 |            |   0   
  16777216 |            |   0   
  33554432 |            |   0   
  67108864 |            |   0   
  134217728 |            |   0   
  268435456 |@@@@@@@@        |   1   
  536870912 |@@@@@@@@        |   1   
 -------------------------------------------------------------------------------  
 Count   nsec Lock             Caller  
   13 41341873 libc.so.1`_uberdata     0  
   
    nsec ---- Time Distribution --- count Stack  
    8192 |@@@           |   2 0  
    16384 |            |   0   
    32768 |@            |   1   
    65536 |@@@@@@@         |   4   
   131072 |@@@           |   2   
   262144 |            |   0   
   524288 |            |   0   
   1048576 |            |   0   
   2097152 |            |   0   
   4194304 |            |   0   
   8388608 |            |   0   
  16777216 |            |   0   
  33554432 |            |   0   
  67108864 |@@@           |   2   
  134217728 |@            |   1   
  268435456 |@            |   1   
 -------------------------------------------------------------------------------  
 Count   nsec Lock             Caller  
   10 47026176 store-id-helper`g_cout_mtx  0  
   
    nsec ---- Time Distribution --- count Stack  
    8192 |@@           |   1 0  
    16384 |            |   0   
    32768 |@@           |   1   
    65536 |@@           |   1   
   131072 |@@           |   1   
   262144 |@@           |   1   
   524288 |            |   0   
   1048576 |            |   0   
   2097152 |            |   0   
   4194304 |            |   0   
   8388608 |            |   0   
  16777216 |            |   0   
  33554432 |            |   0   
  67108864 |@@@@@@@         |   3   
  134217728 |@@@@          |   2   
 -------------------------------------------------------------------------------  
 Count   nsec Lock             Caller  
   1 268435456 0x4497c0           0  
   
    nsec ---- Time Distribution --- count Stack  
  268435456 |@@@@@@@@@@@@@@@@@@@@@@@@|   1 0  
 -------------------------------------------------------------------------------  
 Count   nsec Lock             Caller  
   1 268435456 0x449700           0  
   
    nsec ---- Time Distribution --- count Stack  
  268435456 |@@@@@@@@@@@@@@@@@@@@@@@@|   1 0  
 -------------------------------------------------------------------------------  
 Count   nsec Lock             Caller  
   1 134217728 0x449880           0  
   
    nsec ---- Time Distribution --- count Stack  
  134217728 |@@@@@@@@@@@@@@@@@@@@@@@@|   1 0  
 -------------------------------------------------------------------------------  
 Count   nsec Lock             Caller  
   1  262144 0x449840           0  
   
    nsec ---- Time Distribution --- count Stack  
   262144 |@@@@@@@@@@@@@@@@@@@@@@@@|   1 0  
 -------------------------------------------------------------------------------  
 Count   nsec Lock             Caller  
   1  262144 0x449800           0  
   
    nsec ---- Time Distribution --- count Stack  
   262144 |@@@@@@@@@@@@@@@@@@@@@@@@|   1 0  
 root @ khorne / #   
   

Тут все совершенно очевидно. Нет конкуренции - нет вывода, выводится ноль и все. Во втором случае у нас в ожидании находится свыше 60к процессов, мы видим, что ожидания вызывает именно мьютекс  g_cout_mtx.

Как видите, все просто. Если, при этом, вы видите исходный код, то определение проблемных мест сразу позволяет перейти к их оптимизации.

PS. Интересный факт. Древний (и давно устаревший) top показывает максимум 999 LWP:


Впрочем, в Солярис он отсутствует (его устанавливают отдельно - а потом ему всецело верят, что зря).

А вот штатный prstat показывает все честно, как есть (и в нем нет странных лимитов):



среда, 29 марта 2017 г.

Оптимизация Squid 3+: Часть IV

Конфигурация


Очень часто встречаются несколько распространенных ошибок при конфигурировании Squid, которые принципиально не позволяют достичь сколько-нибудь приемлемой производительности и латентности. Зачастую эти ошибки просто небезопасны.

Характерные примеры подобных ошибок:
  • Слепое копирование default-конфигурации, слепое копирование чужой конфигурации
  • Наполнение конфигурации дефолтными или тупо задранными до максимума значениями
  • Несбалансированные конфигурации, написанные бездумно либо с непропорциональными параметрами, впрямую относящимися к производительности либо ресурсам
  • Неэффективные, чрезмерно избыточные конфигурации с гигантскими ACL либо с избыточно дискретными ACL
  • Несуразные конфигурации - например, чрезмерно гигантский memory cache при cache_dirs по умолчанию или при его отсутствии
Основная причина подобных ошибок - игнорирование концептуальной архитектуры, документации, и непонимание основных принципов работы кэша.

Правила пальца №1. Вдумчиво изучите squid.conf.documented. Пишите свой конфиг с нуля. Структурируйте его. Порядок - важен. Не пишите в него значений по умолчанию, исключая те, которые нужны или полезны для понимания смысла конфигурации.

Параметры кэширования

cache_mem - не задавайте данный параметр равным половине физической памяти или большей ее части. Законы сохранения действуют - где, вы думаете, должна работать ОС и остальные сервисы? Помимо того, что чрезмерно большой кэш может просто быстро привести к панике ядра, он может оказаться медленнее дисковой системы. Кроме того, не забывайте о необходимости памяти для остальных задач кэша, включая обработку запросов, парсинг, кэширование метаданных - все это тоже размещается не в космосе. А кэш метаданных еще и непрерывно прирастает по мере заполнения дискового кэша. 1/8 - 1/4 RAM будет вполне достаточно.

memory_pools - надо понимать, что такое memory pools. Это куча, которую Сквид использует в своих целях: для парсинга запросов, при выполнении операций. Обычно, большинство администраторов не задумываясь задают пул гигантского размера, соизмеримый с cache_mem. Порядка гигабайт. А потом жалуется что памяти нет и начался свопинг. Пулы отключают, и обнаруживают, что память начала "течь", как им кажется. Так вот. Зарубите на носу. Размер пулов памяти пропорционален количеству обрабатываемых запросов в единицу времени. Для средних размеров инсталляции с ~300 клиентских станций за глаза достаточно 50-150 мегабайт пула. Не более. Если пул отключить с целью экономии памяти - начинается trashing memory. То есть память кучи очень сильно фрагментируется и по факту выглядит так, будто память течет - там просто невозможно что-либо выделить, приходится наращивать размер кучи. Об этом сказано и в squid.conf.documented. Таким образом, пул нужен  - не надо лишь делать его чрезмерно большим по принципу "памяти много не бывает". Вы плохо себе представляете, что такое куча размером 8 гигабайт.

memory_replacement_policy heap GDSF
cache_replacement_policy heap LFUDA

Два этих параметра намеренно привожу в оптимальных (в большинстве случаев) значениях. LRU древний и не слишком эффективный алгоритм замещения, так как он игнорирует распределение объектов по размерам. heap-политики почти во всех случаях оказываются более эффективными - раз, и два - в кэше памяти следует размещать по-возможности часто запрашиваемые некрупные объекты, а на дисках - в первую очередь крупные объекты. Рекомендую почитать об алгоритмах GDSF и LFUDA для того, чтобы определить, подходят ли они вам.

maximum_object_size_in_memory 1 MB - ограничивайте размер объектов в кэше памяти. Но и не занижайте чрезмерно эту величину, things changes.

minimum_object_size 10 bytes
maximum_object_size 4 Gb

Мне не кажется хорошей идея набивать кэш веб-багами размером 1 байт (каждый из которых занимает метаданные целого файла), точно так же, как тратить ресурсы на их кэширование. С другой стороны, не стоит хранить редко используемые гигантские объекты - как правило это неразумно.

cache_dir должен соответствовать (хотя бы приблизительно) объему, который в нем хранится. То есть глупо использовать дефолтные параметры иерархии и задавать размер 1 Тб. Пример относительно эффективных параметров cache_dir (в вашем случае может оказаться неэффективным или неработоспособным!):


 cache_dir diskd /data/cache/d1 48000 64 512  
 cache_dir diskd /data/cache/d2 48000 64 512  
 cache_dir diskd /data/cache/d3 48000 64 512  
 cache_dir diskd /data/cache/d4 48000 64 512     

Распределение объектов разных размеров по cache_dir, размещенных на различным образом оптимизированных дисковых массивах (если они используются), целесообразно. Причем cache_dir для мелких объектов следует помещать первыми (порядок важен). В некоторых случаях целесообразно отключить кэшированиие файловых систем (но не во всех).

SSD могут решить проблему узких мест в СХД, если у вас много денег и есть запасные на замену - интенсивность записи при прогреве и replacement дисковых кэшей весьма высокая. Ресурс может оказаться ограничен. Однако выигрыш в скорости может стоить таких затрат.

При избытке оперативной памяти рассмотрите возможность применения RAM disks для наиболее узких мест - например, кэша сертификатов ssl_crtd (предусмотрите сброс его содержимого на жесткие диски при остановке сервисов кэша). Это более целесообразно, нежели выделение гигантского cache_mem.

Списки контроля доступа

Как и в случае firewall, следует избегать написания гигантских ACL в сотни и тысячи элементов. Помимо того, что они занимают много места в памяти и сильно замедляют перезапуск и refresh, они чрезвычайно замедляют обработку запросов (помните? Однопоточное ПО. Обработка этих списков идет последовательно).

Хинт: ACLы на основе регулярных выражений поддаются одной хитрой и неочевидной оптимизации. Группы с вариантами на основе OR (|) работают быстрее, чем последовательный список всех вариантов. То есть, вот эта строка:

 (stnd\-avpg|avs\-avpg|stnd\-ipsg|bash\-avpg)\.crsi\.symantec\.com  

обрабатывается в несколько раз быстрее, чем ее последовательный эквивалент:

 stnd\-avpg\.crsi\.symantec\.com  
 avs\-avpg\.crsi\.symantec\.com  
 stnd\-ipsg\.crsi\.symantec\.com  
 bash\-avpg\.crsi\.symantec\.com\.com  

Хитрость здесь в том, что последовательное применение функции regex_match требует больше времени, имеет больший оверхед и займет более, чем вдвое больше времени, чем вариант, приведенный выше. Как ни странно, выражения OR обрабатываются в одном вызове regex_match более эффективно. 

Несмотря на утверждение некоторых администраторов, что регистронечувствительные ACL работают быстрее, мы с коллегой не согласны с этим утверждением по алгоритмическим причинам и не рекомендуем щедро посыпать списки контроля доступа опцией -i без настоятельной необходимости ее использования. Это может снизить скорость обработки запросов, причем в некоторых случаях весьма значительно.

refresh_pattern

Наличие огромного количества высокодискретных (на каждый домен и поддомен) refresh_pattern - ошибка, сильно бьющая по производительности. Рекомендация точно такая же, как и в предыдущем примере - сокращайте регулярные выражения при помощи групп с (|):

 # Updates: Windows  
 refresh_pattern (windowsupdate|microsoft|windows)\.com/.*\.(cab|exe|ms[i|u|f|p]|[ap]sf|wm[v|a]|dat|zip|psf) 43200 80% 129600 reload-into-ims  
 refresh_pattern microsoft\.com\.akadns\.net/.*\.(cab|exe|ms[i|u|f|p]|[ap]sf|wm[v|a]|dat|zip|psf) 43200 80% 129600 reload-into-ims  
 refresh_pattern deploy\.akamaitechnologies\.com/.*\.(cab|exe|ms[i|u|f|p]|[ap]sf|wm[v|a]|dat|zip|psf) 43200 80% 129600 reload-into-ims     

Правило пальца: строк refresh_pattern должно быть как можно меньше. Избегайте применения опции -i без необходимости.

DNS

Быстрый DNS имеет прямое влияние на производительность прокси. Как ни странно, статистика показывает, что, почти во всех случаях, не имеет смысла сильно увеличивать параметр ipcache_size. Значения 4096 достаточно в обычных (не слишком крупных) конфигурациях.

Гораздо лучше разместить высокоскоростной рекурсор (например, Unbound) прямо на прокси-боксе либо не далее одного хопа от него. И нацелить кэш непосредственно на него. Только не увлекайтесь чрезмерными TTL для кэшированных ответов на рекурсоре - может привести к проблемам с CDN (хотя да, значительно улучшает отклик). Защищайте рекурсор - это вектор для атаки.

Разное

Есть пара параметров, которые могут отказаться полезными в оптимизации дискового кэша:

 # Default is 20  
 store_objects_per_bucket 32  
   
 store_avg_object_size 200 KB  

Хочу, однако, обратить внимание на то, что данные параметры задаются на основе анализа статистики кэша, а не высасываются из пальца. store_avg_object_size, например, задается на основании параметра Mean object size из статистики (обычно ставится численно равным или близким к статистическому показателю). store_objects_per_bucket влияет на структуру swap-файлов, хранящих хэши и может варьироваться для уменьшения оверхеда. Однако не меняется без перестроения swap-файлов (переиндексации кэша) и изменения не заметны немедленно.

Заключение: Волшебной пули не существует

Оптимизация - это кропотливый процесс, связанный с длительным сбором статистики, ее анализом, и подбором необходимых (не всех и не наугад) параметров. Не увлекайтесь. Синдром чрезмерного администрирования - это профессиональное заболевание. :)


вторник, 28 марта 2017 г.

C++: Гетеросексуальный рефакторинг


Я просто оставлю это здесь:

   /* Alien-faggot-style and bwaaaaaaaaah! */  
   /* 0,25 ms evaluation time on Xeon */  
   /* 1 ms evaluation time on SPARC */  
   if (!(!s1.empty() && s2.empty())) {  
    .....some stuff...  
   } else {  
    .....some stuff...  
   }  
     
   /* Heterosexual refactoring :) */  
   /* Less 0,15 ms on Xeon */  
   if (!s2.empty()) {  
    .....some stuff...  
   } else if (!s1.empty()) {  
    .....some stuff...  
   }  
   
   /* Heterosexual refactoring 2 - best ever :) */  
   /* Less 0,05 ms on Xeon */  
   if (!s2.empty()) {   
    .....some stuff...  
   } else { /* else if (!s1.empty()) */  
    .....some stuff...  
   }  

Ну, вы поняли :) Не надо писать булевского ужаса :) Понятное дело, что тут рефакторинг зависит от внешних условий, которые на примере не показаны, однако он работает. И - да, в последнем примере хорошим тоном будет оставить комментарий в else, иначе логика неочевидна. :)


понедельник, 6 марта 2017 г.

C++: Измерение интервалов с использованием steady_clock

Библиотека sys/time.h, она же Манька Аблигация  ctime - на самая лучшая штука, когда нужно не абсолютное время (с использованием gettimeofday() ), а интервальные замеры - например, для профилирования и тому подобных задач.

Лишние движения арифметические и тому подобное.

Кроме того, время может прыгать (например, при выполнении синхронизаций NTP).

Логически более правильно для подобных задач воспользоваться steady_clock - в большинстве реализаций они действительно steady. Да и считать в коде придется чуть меньше.

Простенько и со вкусом:

 #include <chrono>  
   
 std::chrono::steady_clock::time_point t1, t2;  
 std::chrono::duration<double> elapsedTime;  
   
 int main(int argc, char* argv[])  
 {  
   
  t1 = std::chrono::steady_clock::now();  
   
   /* Some stuff */  
   
  t2 = std::chrono::steady_clock::now();  
   
  /* Compute elapsed time in ms */  
  std::chrono::duration<double, std::milli> elapsedTime = t2 - t1;     /* Fractional duration: no duration_cast needed */  
   
  std::cout << "Time: " << std::to_string(elapsedTime.count()) << " ms" << std::endl;  
 }  
   

Для контраста посмотрим, что было раньше:

 #include <ctime>     /* for gettimeofday() */  
   
 timeval t1, t2;  
 double elapsedTime;  
   
 int main(int argc, char* argv[])  
 {  
   
  gettimeofday(&t1, nullptr);  
   
  /* Some stuff */  
    
  gettimeofday(&t2, nullptr);  
   
   
  /* Compute elapsed time in ms */  
  elapsedTime = (t2.tv_sec - t1.tv_sec) * 1000.0;          /* sec to ms */  
  elapsedTime += (t2.tv_usec - t1.tv_usec) / 1000.0;     /* us to ms */  
        
  std::cout << "Time: " << std::to_string(elapsedTime) << " ms" << std::endl;  
   
 }  
   

Не слишком изящно, не правда ли?

среда, 22 февраля 2017 г.

C++: Эффективная разбивка строки на токены

По интернету гуляет два основных варианта токенайзера строк (strotk() мы не рассматриваем, он из С и корявый, а использование StringStream это для недоумков и тормозов. Ну а Boost для такой задачи тащить это карандаш мельничным жерновом затачивать):

 // Many delimiters possible, null-tokens protection  
 std::vector<std::string> split(const std::string &str, const std::string &delims)  
 {  
   std::vector<std::string> tokens;  
   std::size_t start = str.find_first_not_of(delims), end = 0;  
   
   while((end = str.find_first_of(delims, start)) != std::string::npos)  
   {  
     tokens.push_back(str.substr(start, end - start));  
     start = str.find_first_not_of(delims, end);  
   }  
   if (start != std::string::npos)  
     tokens.push_back(str.substr(start));  
   
   return tokens;  
 }  
   
 // One delimiter only  
 std::vector<std::string> split(const std::string &str, const std::string &delim)  
 {  
      std::vector<std::string> tokens;  
      size_t prev = 0, pos = 0;  
      do  
      {  
           pos = str.find(delim, prev);  
           if (pos == std::string::npos)  
                pos = str.length();  
           std::string token = str.substr(prev, pos-prev);  
           if (!token.empty())  
                tokens.push_back(token);  
           prev = pos + delim.length();  
      }  
      while (pos < str.length() && prev < str.length());  
      return tokens;  
 }  
   

Народ их тупо копипастит и радуется, что удачно стырил готовый код и думать не надо.

Ага, щаз. Второй вариант кажется подходящим в большинстве случаев, кроме того, простейший тайминг показывает, что он раза в три минимум быстрее первого (ага, цена итераторов и вообще ООП).

И что, думаете, он эффективен по максимуму?

Как бы не так! Никто не дает себе труда вчитаться в код и поиграть с ним, прежде, чем использовать.

Вот оптимальный вариант:

 // Optimized split  
 // One delimiter only  
 std::vector<std::string> split(const std::string &str, const std::string &delim)  
 {  
      std::vector<std::string> tokens;  
      size_t prev = 0, pos = 0;  
      do {  
           pos = str.find(delim, prev);  
           if (pos == std::string::npos)  
                pos = str.length();  
           if (!str.substr(prev, pos-prev).empty())  
                tokens.push_back(str.substr(prev, pos-prev));  
           prev = pos + delim.length();  
      } while (pos < str.length());  
      return tokens;  
 }  
   

Не надо конструировать контейнеры в цикле - это раз, и два - выходное условие было избыточным. Вы бы это тоже знали, если бы потестили функцию сперва. И вообще, недоумки, когда копипастите код с StackOverflow, хотя бы вчитывайтесь в него. После оптимизации она минимум втрое эффективнее, чем до (да, профилирование показывает не менее, чем в три раза в среднем).

Ну и помните про реентрантность - с вектором она нереентрантна (см. предыдущую статью).

PS. Да, мне известны недостатки этого алгоритма. Он работает только с одним разделителем, и два разделителя подряд дают пустой токен. Но он экстремально быстр, а его недостатки мне не мешают. Кроме того, он легко трансформируется в потокобезопасный и реентрантный вариант - правда, с ограничением по числу токенов и удобству. Во всех остальных случаях можете тяжко вздохнуть и взять вариант 1. Кстати, его тоже можно оптимизировать. Если немного подумать.

пятница, 17 февраля 2017 г.

std::vector is non-reentrant

Я говорил, что, в отличие от разработчиков Boost, разработчики STL хотя бы люди? Я хочу взять свои слова назад.

Это история ловли одного бага, которая заняла свыше трех недель. При этом баг не воспроизводился, в лабораторных условиях воссоздать его не удалось, а документация и прочие источники не дали ничего вразумительного.

Взгляните на код:

 void processdata()  
 {  
      std::vector<std::string> inToken;  
   
  // Some stuff  
   
  for (size_t i = 1; i < v_max; i++) {  
     // Some stuff  
                     inToken.push_back(token);  
      }  
                       
  // Some stuff  
   
 }
  
 int main(int argc, char* argv[])  
 {  
   
  // Some stuff  
    
      while (!std::cin.eof()) {  
           /* Start processing threads */  
           for (size_t i = 0; i < v_max_threads; i++) {  
                threads.push_back(std::thread([]() {  
                     processdata();  
                }));  
           }  
           /* Finish all threads */  
           std::for_each(threads.begin(), threads.end(), [](std::thread &t) {  
                t.join();  
           });  
           threads.clear();  
      }  
      /*-------------Main Loop----------------*/  
      return 0;  
 }  

Все красиво, не правда ли? Полностью локальный вектор в тредовой процедуре. В этой процедуре вектор используется для парсинга строк (std::string) переменной длины. Конкретно - строка разбивается на токены, причем первый токен коротенький, и его длина не превышает нескольких символов, второй может варьироваться от 20 до 2048 символов, изредка - более.

 Все хорошо и прекрасно, но иногда, вне какой либо связи с нагрузкой или любыми другими видимыми событиями, данное приложение сегфолтилось. Причем GDB при обратной трассировке показывал только выход из процессингового треда по join() (приложение было собрано с ключом -g), а DBX давал странную трассировку:

 t@336 (l@336) terminated by signal SEGV (no mapping at the fault address)  
 Current function is std::__detail::_Executor<__gnu_cxx::__normal_iterator<const char*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::__cxx11::sub_match<__gnu_cxx::__normal_iterator<const char*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, std::__cxx11::regex_traits<char>, true>::_M_dfs  
  795     { return *(this->_M_impl._M_start + __n); }  
 
 (dbx) where  
 current thread: t@336  
 =>[1] std::__detail::_Executor<__gnu_cxx::__normal_iterator<const char*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::__cxx11::sub_match<__gnu_cxx::__normal_iterator<const char*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, std::__cxx11::regex_traits<char>, true>::_M_dfs(this = 0xfffffd7ffea90ef0, __match_mode = _Match_mode::_Prefix, __i = 17), line 795 in "stl_vector.h"  
  [2] std::__detail::_Executor<__gnu_cxx::__normal_iterator<const char*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::__cxx11::sub_match<__gnu_cxx::__normal_iterator<const char*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >, std::__cxx11::regex_traits<char>, true>::_M_dfs(this = 0xfffffd7ffea90ef0, __match_mode = <value unavailable>, __i = <value unavailable>), line 267 in "regex_executor.tcc"  

Смотрите очень внимательно. Regex тут не при чем. Дамп в принципе не содержит отсылки к коду приложения, то есть исключение прилетает не оттуда. Что-то с аллокатором, да? Какой-то контейнер вызывает проблему.


Опущу историю ловли бага. Беда в том, что он проявлялся спорадически. Иногда несколько раз за день, иногда - ни разу. Никакой системы не было. Воспроизвести в контролируемых условиях, как я сказал выше, не удалось.

Посмотрите на код еще раз.

Так вот. несмотря на то, что тип вектор - является, по заявлению разработчиков, потокобезопасным - то есть читатель читателю в разных тредах не мешает, писатель читателю в разных тредах не мешает, а в нашем случае, раз вектор локальный, вообще никаких проблем быть не должно - он не является реентрантным.

Говоря простым языком, у контейнера std::vector есть аллокатор памяти, который общий для всех векторов, которые вы определяете - неважно, локальные или глобальные.

И, когда происходит push_back в вектор, иногда перераспределяется память (помните, я говорил выше, что вектор используется для строк переменной длины?). Так вот, поскольку реализация аллокатора сделана не совсем людьми, никто о реентрантности и не думал. Более того, в разных системах, с разными libc и разными реализациями вышеприведенный код может вести себя как угодно. Если какие-нибудь алиены в дистрибутиве Бла-Бла-Бла-Линукс вдруг озаботились реентрантностью, они могли использовать примитивы синхронизации в аллокаторе контейнеров. Хорошо, если мьютексы. Много хуже, если спин-локи. Если не озаботились - получайте сегфолт в любой момент времени. На одной платформе мы не получали сегфолтов, но процессор взлетал в момент вызова аллокатора до 50 или 100% резким и кратковременным пиком. Что сказывалось на работе всего сервера, поскольку происходило достаточно часто.

В нашем случае, когда в двух разных асинхронных тредах одновременно происходил push_back в два - напоминаю, разных локальных вектора - в случае, если вставляемые строки сильно отличались по размеру, в одном из тредов или в двух одновременно происходил вызов нереентрантнолго аллокатора, что приводило к повреждению памяти, повреждению вставляемых строк и обычно оба треда падали в дамп (они обычно попарно и падали, сегфолты почти всегда происходили парами).


Окей, чтобы убедиться что я прав, давайте закостылим:

 std::mutex vec_mtx;  
   
 void processdata()  
 {  
      std::vector<std::string> inToken;  
   
  // Some stuff  
   
  for (size_t i = 1; i < v_max; i++) {  
     // Some stuff  
                {  
                     std::lock_guard<std::mutex> lock(vec_mtx);  
                     inToken.push_back(token);  
                }  
   
      }  
                       
  // Some stuff  
   
      }  
   
 int main(int argc, char* argv[])  
 {  
   
  // Some stuff  
    
      while (!std::cin.eof()) {  
           /* Start processing threads */  
           for (size_t i = 0; i < v_max_threads; i++) {  
                threads.push_back(std::thread([]() {  
                     processdata();  
                }));  
           }  
           /* Finish all threads */  
           std::for_each(threads.begin(), threads.end(), [](std::thread &t) {  
                t.join();  
           });  
           threads.clear();  
      }  
      /*-------------Main Loop----------------*/  
      return 0;  
 }  

Обернем push_back в мьютекс.

Ой! Что мы видим! Сегфолт ушел!

Одно плохо. Скорость выполнения тоже ухудшилась. Примерно в 3-30 раз. В зависимости от данных.

Не говорите мне, что надо было делать вызов Вектор.reserve() перед вставками. Во-первых, это не помогает, а во-вторых, это не имеет отношения к проблеме. К производительности push_back - может быть, но не к реентрантности.

Решение плохое во всех отношениях. Точка сериализации. Конкуренция блокировок.

Рассматривались разные варианты. Распределять контейнер самописным аллокатором, использовать динамический массив, использовать другой тип контейнеров.

Все было коряво, дьявольски усложняло алгоритмику, вело к утечкам (потенциальным) памяти итд. итп.

В конечном итоге проблему удалось решить радикально, переписав процедуру processdata() полностью без векторов, с использованием лишь наиболее примитивных контейнеров. Мне повезло, что в векторе могло быть от 1 до 4 элементов. Поэтому я отделался малой кровью лишь слегка изменив алгоритм и затолкав элементы в нумерованные переменные типа строка.



Но представьте себе, что вам надо обработать в потоке больше 100 элементов? Представили? Я как-то плохо представляю себе подобное без массив-подобных структур. Более того, я очень плохо представляю себе потоковую обработку, где я ограничен самыми примитивными типами данных из-за реентрантности.

Тщательный (очень тщательный поиск!) привел лишь к одной-единственной ссылке, где в принципе упоминалась теоретическая возможность (лишь возможность!) подобной ситуации. При этом все гуру СПО/GCC/STL всюду вещают, что контейнеры - и векторы в частности - потокобезопасные, нигде не сообщая, что они могут оказаться нереентрантными. (я умолчу о том, что в СПО в принципе дело плохо с документированием. Читайте исходники. Вы видели исходники STL? :)).

Да, я знаю, что можно обойти проблему, написав поток на C и на самом низком уровне - строки как цепочки указателей на символы, или написать свой аллокатор - реентрантный - для одного-единственного приложения.

Вся суть проблемы совершенно понятна - мышление уровня IBM PC с одним-единственным процессором Pentium-D ведет к тому, что программист, пишущий инструментальные средства, принципиально не задумывается, во-первых, обо всех возможных вариантах программирования, а, во-вторых, не представляет, что код может исполняться более, чем в один поток более, чем одним процессорным ядром. Не стоит говорить - "пиши иначе". В данной конкретной задаче иначе - не получится. Надо работать с локальными векторами и баста. И у меня все равно не укладывается в голове, как можно иметь локальную структуру, которая заявлена как потокобезопасная, но имеет глобальный некопируемый аллокатор, который никаким вывертом невозможно сделать локальным. И ради одной небольшой подпрограммы надо либо писать собственный класс контейнера + аллокатор. Если это прелести объектно-ориентированного программирования, то я - за процедурный подход.

Резюмируя вышенаписанное, я утверждаю, что разработчики STL - это дети осла, свиньи и Чужого из фильма "Чужой-4" (таким образом, они лишь на 1/3 генетически люди):


Поздоровайтесь с родителем, упыри! Это один из ваших отцов.