Функция Sybase ESQL/C FETCH ct_fetch() зависает навсегда
Я сталкиваюсь с проблемой, что в огромном бизнес-приложении, написанном на C/C++ и использующем Sybase ESQL/C в качестве dblayer, операция FETCH зависает при вызове во второй раз (то есть первый FETCH возвращает строку в порядке). Я расширил сгенерированный C-код, чтобы узнать, где именно зависание. Функция ct_fetch() не возвращается. Наблюдение за выполненными системными вызовами с помощью команды truss в Solaris показывает, что зависание происходит из-за отсутствия данных отправки на сервер Sybase, ct_fetch() просто приводит к вызову read() sys в сети, а плохой сервер Sybase не Знайте, что он должен что-то сказать, следующий ряд или тот факт, что больше нет рядов.
На первом FETCH, который работает нормально, функция ct_fetch() отправляет 16-байтовый запрос и считывает ответ сервера Sybase, данные строки в переменных хоста.
Ниже я привел симуляцию, то есть отрывок того, что выдает приложение, вместе взятый в виде небольшого pgm ESQL / C, который делает то же самое, что и приложение в этой ситуации. Небольшая демонстрация работает нормально, как и ожидалось.
Я не знаю, как отладить и зафиксировать это, поскольку у меня нет C-кода функции ct_fetch() и я не вижу, что там не так. Есть намеки?
Обновление информации № 1
После почти недели отладки / тестирования различных идей у меня появилось некоторое обновление:
CTlibrary в $SYBASE/OCS-15_0/devlib/libsybct64.so позволяет установить некоторые переменные окружения, чтобы включить отладку вызовов ct_*() с помощью (комментарии показывают возможные значения):
# CS_DBG_ALL
# CS_DBG_API_LOGCALL
# CS_DBG_API_STATES
# CS_DBG_ASYNC
# CS_DBG_DIAG
# CS_DBG_ERROR
# CS_DBG_MEM
# CS_DBG_NETWORK
# CS_DBG_PROTOCOL
# CS_DBG_PROTOCOL_FILE
# CS_DBG_PROTOCOL_STATES
# CS_DBG_SSL
SYBOCS_DEBUG_FLAGS=CS_DBG_API_LOGCALL, \
CS_DBG_API_STATES, \
CS_DBG_ERROR, \
CS_DBG_NETWORK, \
CS_DBG_PROTOCOL
SYBOCS_DEBUG_LOGFILE=/home/sisis/sybocs.log
export SYBOCS_DEBUG_FLAGS SYBOCS_DEBUG_LOGFILE
захват сеанса таким образом в файле sybocs.log
и объединение его с проверенными системными вызовами показывает ту же проблему, отправка в сеть отсутствует во второй операции FETCH:
1. FETCH:
24640: ( ) i n v o k i n g c t _ f e t c h ( ) \n
24640: write(5, " n p ( 1 5 0 0 4 7 , 0".., 78) = 78
24640: write(5, " n p ( 1 5 0 0 4 8 , 0".., 71) = 71
sybocs.log:
np(150047, 0x1001a2e10, 0x1001ca790): np_io_send(): Initiating send operation
np(150048, 0x1001a2e10, 0x1001ca790): np__io_send(): Calling net_write
24640: send(7, 0x1001C9100, 16, 0) = 16
24640: 0F01\010\0\0\0\082\005\0F3\0B301
24640: write(5, " n p ( 1 5 0 0 4 9 , 0".., 65) = 65
24640: write(5, " n p ( 1 5 0 0 5 0 , 0".., 58) = 58
24640: write(5, " n p ( 1 5 0 0 5 1 , 0".., 69) = 69
24640: write(5, " n p ( 1 5 0 0 5 2 , 0".., 72) = 72
sybocs.log:
np(150049, 0x1001a2e10, 0x1001ca790): np__send_cb(): Posting I/O
np(150050, 0x1001a2e10, 0x1001ca790): np_io_read(): entry
np(150051, 0x1001a2e10, 0x1001ca790): np_io_read(): full buffer read
np(150052, 0x1001a2e10, 0x1001ca790): np_io_read(): calling sybnet_read
24640: read(7, 0x10049B7B0, 8192) Err#11 EAGAIN
24640: pollsys(0xFFFFFFFF7FFFBDB4, 1, 0xFFFFFFFF7FFFBC40, 0x00000000) = 1
24640: read(7, 0x10049B7B0, 8192) = 276
2. FETCH:
24640: ( ) i n v o k i n g c t _ f e t c h ( ) \n
24640: write(5, " n p ( 1 5 0 2 2 9 , 0".., 58) = 58
24640: write(5, " n p ( 1 5 0 2 3 0 , 0".., 69) = 69
24640: write(5, " n p ( 1 5 0 2 3 1 , 0".., 72) = 72
sybocs.log:
np(150229, 0x1001a2e10, 0x1001ca790): np_io_read(): entry
np(150230, 0x1001a2e10, 0x1001ca790): np_io_read(): full buffer read
np(150231, 0x1001a2e10, 0x1001ca790): np_io_read(): calling sybnet_read
24640: read(7, 0x10049B7B0, 8192) Err#11 EAGAIN
24640: pollsys(0xFFFFFFFF7FFFBDB4, 1, 0xFFFFFFFF7FFFBC40, 0x00000000) = 0
Обновление информации № 2
Установка значения конфигурации ASE compatibility mode
до 1 делает вышеупомянутую проблему исчезнуть:
1> sp_configure "enable compatibility mode", 1
2> go
установка 0, заставляет FETCH зависать.
Время поднять проблему с Sybase (например, SAP).
/*
** example1.cp
**
** This example is an interactive query program that
** guides the user through a series of prompts to select
** a title from the titles table in the pubs2 database.
** It uses cursors extensively to guide the query.
**
** This example uses the classical style of indicator
** handling using a hostvar:indicvar combo. See function
** show_book() for more information.
*/
#include
#include "sybsqlex.h"
/* Declare the SQLCA. */
EXEC SQL INCLUDE SQLCA;
#define EC_rnummer 41 /* eigentlich aus rechkopf.h: #define EC_rnummer 41 */
#define EC_wkz 4 /* eigentlich aus rechkopf.h: #define EC_wkz 4 */
#define EC_geltung 21 /* eigentlich aus rechsammel.h: #define EC_geltung 21 */
#define EC_norm_bez 251 /* eigentlich aus acq_lieferant.h: #define EC_norm_bez 251 */
#define EC_decimal (32+2+1)
EXEC SQL BEGIN DECLARE SECTION;
static struct hostRechsuche_out_t
{
int rart;
long rnr;
char rechnr[EC_rnummer];
int liefgroup;
long liefnr;
CS_DATETIME rechdate;
CS_DATETIME ivdate;
char rechwkz[EC_wkz];
float kurs;
int grechstatus;
short hjahr;
long anzahl;
char betrag[EC_decimal];
char dmbetrag[EC_decimal];
long porto;
long spesen;
int entsteuer;
long aussteuer;
int rabatt;
long sonstiges;
char geltung[EC_geltung];
char norm_bez[EC_norm_bez];
int brgroup;
}
hostRechsuche_out;
EXEC SQL END DECLARE SECTION;
/*
** These tokens must be declared in a declare section
** because they are used in declare sections below.
*/
EXEC SQL BEGIN DECLARE SECTION;
#define TYPESIZE 13
#define TIDSIZE 6
EXEC SQL END DECLARE SECTION;
#define EOLN '\0'
/*
** Forward declarations of the error and message handlers and
** other subroutines called from main().
*/
void error_handler();
void warning_handler();
void cursor();
#define ISWORDSPACE(c) (c == ' ' || c == '\t')
int
main(int argc, char *argv[])
{
EXEC SQL BEGIN DECLARE SECTION;
/* storage for login name and password. */
char username[30];
char password[30];
EXEC SQL END DECLARE SECTION;
EXEC SQL WHENEVER SQLERROR CALL error_handler();
EXEC SQL WHENEVER SQLWARNING CALL warning_handler();
EXEC SQL WHENEVER NOT FOUND CONTINUE;
/*
** Copy the user name and password defined in sybsqlex.h to
** the variables declared for them in the declare section.
*/
strcpy(username, "sisis");
strcpy(password, "sisis123");
EXEC SQL CONNECT :username IDENTIFIED BY :password;
EXEC SQL USE bfbsis;
cursor();
EXEC SQL DISCONNECT DEFAULT;
return(STDEXIT);
}
void
cursor()
{
EXEC SQL BEGIN DECLARE SECTION;
char s_sqlkommando[20000]; /* for variable select */
long hostAnzahl;
long host_rnr;
EXEC SQL END DECLARE SECTION;
strcpy(s_sqlkommando, "SELECT DISTINCT 2 rart, \
rechkopf.rnr, \
rechkopf.rnummer, \
rechkopf.liefgroup, \
rechkopf.liefnr, \
rechkopf.rdatum, \
rechkopf.ivdatum, \
rechkopf.wkz, \
rechkopf.kurs, \
rechkopf.grechstatus, \
isnull(max(acq_haushalt.hjahr),0), \
0 sollanz, \
isnull(sum(acq_booking.amount),0) betrag, \
isnull(sum(acq_booking.stdamount),0) dmbetrag, \
rechsammel.porto porto, \
rechsammel.spesen spesen, \
rechsammel.entsteuer entsteuer, \
rechsammel.aussteuer aussteuer, \
rechsammel.rabatt rabatt, \
rechsammel.sonstiges sonstiges, \
rechsammel.geltung geltung, \
acq_lieferant.norm_bez, \
rechkopf.brgroup \
FROM rechkopf, rechsammel, rechnung, acq_haushalt, rechbuch, acq_booking, acq_lieferant \
WHERE rechkopf.rnr = rechsammel.rnr \
AND rechnung.rnr =* rechkopf.rnr \
AND acq_haushalt.hnr =* acq_booking.hnr \
AND rechbuch.rnr =* rechnung.rnr \
AND rechbuch.lfdnr =* rechnung.lfdnr \
AND acq_booking.bnr =* rechbuch.bnr \
AND acq_booking.exemplar =* rechbuch.exemplar \
AND acq_booking.band =* rechbuch.band \
AND acq_booking.rnr =* rechbuch.rnr \
AND acq_booking.lfdnr =* rechbuch.lfdnr \
AND rechkopf.brgroup = acq_lieferant.brgroup \
AND rechkopf.liefgroup = acq_lieferant.gruppe \
AND rechkopf.liefnr = acq_lieferant.nr \
AND rechkopf.brgroup = 0 \
AND rechkopf.liefgroup = 1 \
AND rechkopf.liefnr = 31 \
AND rechkopf.rnummer = '20816154' \
AND rechkopf.rdatum = '06.04.2017' \
AND rechkopf.grechstatus IN (48,112,144,268435504,268435568,268435600) \
GROUP BY rechkopf.rnr,rechkopf.rnummer,rechkopf.liefgroup,rechkopf.liefnr, \
rechkopf.rdatum, rechkopf.ivdatum, rechkopf.wkz, rechkopf.kurs, rechkopf.grechstatus, \
rechsammel.porto, rechsammel.spesen, rechsammel.entsteuer, rechsammel.aussteuer, \
rechsammel.rabatt, rechsammel.sonstiges, rechsammel.geltung, \
acq_lieferant.norm_bez, rechkopf.brgroup \
ORDER BY 6 DESC, 3 DESC, 4 DESC, 5 DESC");
host_rnr = 0;
EXEC SQL SELECT COUNT(*) INTO :hostAnzahl FROM acq_rechnotiz WHERE rnr = :host_rnr;
/* Declare a cursor */
EXEC SQL PREPARE sid_Rechsuche FROM :s_sqlkommando ;
EXEC SQL DECLARE Rechsuche_cur CURSOR FOR sid_Rechsuche ;
/* Open the cursor. */
EXEC SQL OPEN Rechsuche_cur;
/*
** FETCH time
*/
for (;;)
{
EXEC SQL FETCH Rechsuche_cur INTO :hostRechsuche_out ;
if (sqlca.sqlcode == 100) {
printf("FETCH: sqlca.sqlcode == 100\n");
break;
}
printf("rechnr: %s\n", hostRechsuche_out.rechnr);
}
/*
** Close the cursor and return
*/
EXEC SQL CLOSE Rechsuche_cur;
}
/*
** void error_handler()
**
** Displays error codes and numbers from the SQLCA and exits with
** an ERREXIT status.
*/
void
error_handler(void)
{
fprintf(stderr, "\n** SQLCODE=(%ld)", sqlca.sqlcode);
if (sqlca.sqlerrm.sqlerrml)
{
fprintf(stderr, "\n** ASE Error ");
fprintf(stderr, "\n** %s", sqlca.sqlerrm.sqlerrmc);
}
fprintf(stderr, "\n\n");
exit(ERREXIT);
}
/*
** void warning_handler()
**
** Displays warning messages.
*/
void
warning_handler(void)
{
if (sqlca.sqlwarn[1] == 'W')
{
fprintf(stderr,
"\n** Data truncated.\n");
}
if (sqlca.sqlwarn[3] == 'W')
{
fprintf(stderr,
"\n** Insufficient host variables to store results.\n");
}
return;
}