Fixed an issue where the debugger hangs when stepping into nested function/procedure. #8443

pull/8619/head
Khushboo Vashi 2025-04-03 19:20:28 +05:30 committed by GitHub
parent 8cf14222ff
commit 8031c35160
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 129 additions and 81 deletions

View File

@ -41,6 +41,7 @@ MODULE_NAME = 'debugger'
# Constants
PLDBG_EXTN = 'pldbgapi'
ASYNC_OK = 1
BUSY = 3
DEBUGGER_SQL_PATH = 'debugger/sql'
DEBUGGER_SQL_V1_PATH = 'debugger/sql/v1'
DEBUGGER_SQL_V3_PATH = 'debugger/sql/v3'
@ -263,6 +264,9 @@ def index():
def execute_dict_search_path(conn, sql, search_path):
if conn.transaction_status() == 1:
return True, BUSY
sql_search = SET_SEARCH_PATH.format(search_path)
status, res = conn.execute_void(sql_search)
@ -276,6 +280,9 @@ def execute_dict_search_path(conn, sql, search_path):
def execute_async_search_path(conn, sql, search_path):
if conn.transaction_status() == 1:
return True, BUSY
sql_search = SET_SEARCH_PATH.format(search_path)
status, res = conn.execute_void(sql_search)
@ -1214,18 +1221,24 @@ def execute_debugger_query(trans_id, query_type):
status, result = execute_async_search_path(
conn, sql, de_inst.debugger_data['search_path'])
if result == BUSY:
return make_json_response(
data={'status': 'Busy', 'result': []}
)
if result and 'select() failed waiting for target' in result:
status = True
result = None
if not status:
return internal_server_error(errormsg=result)
return make_json_response(
data={'status': status, 'result': result}
)
status, result = execute_dict_search_path(
conn, sql, de_inst.debugger_data['search_path'])
if not status:
return internal_server_error(errormsg=result)
if query_type == 'abort_target':
@ -1234,6 +1247,11 @@ def execute_debugger_query(trans_id, query_type):
data={'status': 'Success', 'result': result}
)
if result == BUSY:
return make_json_response(
data={'status': 'Busy', 'result': []}
)
return make_json_response(
data={'status': 'Success', 'result': result['rows']}
)
@ -1356,6 +1374,11 @@ def start_execution(trans_id, port_num):
if not status_port:
return internal_server_error(errormsg=res_port)
if res_port == BUSY:
return make_json_response(
data={'status': 'Busy', 'result': []}
)
de_inst.debugger_data['restart_debug'] = 0
de_inst.debugger_data['frame_id'] = 0
de_inst.debugger_data['exe_conn_id'] = exe_conn_id
@ -1430,6 +1453,11 @@ def set_clear_breakpoint(trans_id, line_no, set_type):
if not status:
return internal_server_error(errormsg=res_stack)
if res_stack == BUSY:
return make_json_response(
data={'status': 'Busy', 'result': []}
)
# For multilevel function debugging, we need to fetch current selected
# frame's function oid for setting the breakpoint. For single function
# the frame id will be 0.
@ -1450,9 +1478,16 @@ def set_clear_breakpoint(trans_id, line_no, set_type):
status, result = execute_dict_search_path(
conn, sql, de_inst.debugger_data['search_path'])
result = result['rows']
if not status:
return internal_server_error(errormsg=result)
if result == BUSY:
return make_json_response(
data={'status': 'Busy', 'result': []}
)
result = result['rows']
else:
status = False
result = SERVER_CONNECTION_CLOSED
@ -1536,6 +1571,11 @@ def clear_all_breakpoint(trans_id):
conn, sql, de_inst.debugger_data['search_path'])
if not status:
return internal_server_error(errormsg=result)
if result == BUSY:
return make_json_response(
data={'status': 'Busy', 'result': []}
)
result = result['rows']
else:
return make_json_response(data={'status': False})
@ -1599,6 +1639,11 @@ def deposit_parameter_value(trans_id):
if not status:
return internal_server_error(errormsg=result)
if result == BUSY:
return make_json_response(
data={'status': 'Busy', 'result': []}
)
# Check if value deposited successfully or not and depending on
# the result, return the message information.
if result['rows'][0]['pldbg_deposit_value']:
@ -1670,6 +1715,12 @@ def select_frame(trans_id, frame_id):
status, result = execute_dict_search_path(
conn, sql, de_inst.debugger_data['search_path'])
if result == BUSY:
return make_json_response(
data={'status': 'Busy', 'result': []}
)
if not status:
return internal_server_error(errormsg=result)
else:

View File

@ -66,21 +66,34 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
eventBus.current.fireEvent(DEBUGGER_EVENTS.GET_TOOL_BAR_BUTTON_STATUS, { disabled: false });
};
const getExecuteQueryUrl = (transId, queryType) => {
return url_for('debugger.execute_query', {
'trans_id': transId,
'query_type': queryType,
});
};
const isSuccess = (httpStatus) => {
return httpStatus.data.data.status === 'Success';
};
const isBusy = (httpStatus) => {
return httpStatus.data.data.status === 'Busy';
};
// Function to get the breakpoint information from the server
const getBreakpointInformation = (transId, callBackFunc) => {
let result = '';
// Make ajax call to listen the database message
let baseUrl = url_for('debugger.execute_query', {
'trans_id': transId,
'query_type': 'get_breakpoints',
});
api({
url: baseUrl,
url: getExecuteQueryUrl(transId, 'get_breakpoints'),
method: 'GET',
})
.then(function (res) {
if (res.data.data.status === 'Success') {
if (isBusy(res)) {
getBreakpointInformation(transId, callBackFunc);
} else if (isSuccess(res)) {
result = res.data.data.result;
if (callBackFunc) {
callBackFunc(result);
@ -189,11 +202,11 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
method: 'GET',
})
.then(function (res) {
if (res.data.data.status === 'Success') {
if (isSuccess(res)) {
enableToolbarButtons();
// If status is Success then find the port number to attach the executer.
startExecution(transId, res.data.data.result);
} else if (res.data.data.status === 'Busy') {
} else if (isBusy(res)) {
// If status is Busy then poll the result by recursive call to the poll function
messages(transId);
} else if (res.data.data.status === 'NotConnected') {
@ -224,7 +237,9 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
method: 'GET',
})
.then(function (res) {
if (res.data.data.status === 'Success') {
if (isBusy(res)) {
startExecution(transId, port_num);
} else if (isSuccess(res)) {
// If status is Success then find the port number to attach the executer.
executeQuery(transId);
} else if (res.data.data.status === 'NotConnected') {
@ -244,17 +259,12 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
const executeQuery = (transId) => {
// Make ajax call to listen the database message
let baseUrl = url_for(
'debugger.execute_query', {
'trans_id': transId,
'query_type': 'wait_for_breakpoint',
});
api({
url: baseUrl,
url: getExecuteQueryUrl(transId, 'wait_for_breakpoint'),
method: 'GET',
})
.then(function (res) {
if (res.data.data.status === 'Success') {
if (isSuccess(res)) {
// set the return code to the code editor text area
if (
res.data.data.result[0].src != null &&
@ -388,24 +398,26 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
})
.catch(raiseClearBrekpointError);
};
// Make ajax call to listen the database message
let baseUrl = url_for('debugger.execute_query', {
'trans_id': params.transId,
'query_type': 'get_breakpoints',
});
api({
url: baseUrl,
method: 'GET',
})
.then(function (res) {
if (res.data.data.status === 'Success') {
let result = res.data.data.result;
clearBreakpoint(result);
} else if (res.data.data.status === 'NotConnected') {
raiseFetchingBreakpointError();
}
let get_breakpoint = () => {
// Make ajax call to listen the database message
api({
url: getExecuteQueryUrl(params.transId, 'get_breakpoints'),
method: 'GET',
})
.catch(raiseFetchingBreakpointError);
.then(function (res) {
if (isBusy(res)) {
get_breakpoint();
} else if (isSuccess(res)) {
let result = res.data.data.result;
clearBreakpoint(result);
} else if (res.data.data.status === 'NotConnected') {
raiseFetchingBreakpointError();
}
})
.catch(raiseFetchingBreakpointError);
};
get_breakpoint();
};
@ -419,13 +431,8 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
const stopDebugging = () => {
disableToolbarButtons();
// Make ajax call to listen the database message
let baseUrl = url_for(
'debugger.execute_query', {
'trans_id': params.transId,
'query_type': 'abort_target',
});
api({
url: baseUrl,
url: getExecuteQueryUrl(params.transId, 'abort_target'),
method: 'GET',
})
.then(function (res) {
@ -627,7 +634,7 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
method: 'GET',
})
.then(function (res) {
if (res.data.data.status === 'Success') {
if (isSuccess(res)) {
if (res.data.data.result == undefined) {
/*
"result" is undefined only in case of EDB procedure.
@ -655,7 +662,7 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
} else {
updateResultAndMessages(res);
}
} else if (res.data.data.status === 'Busy') {
} else if (isBusy(res)) {
// If status is Busy then poll the result by recursive call to
// the poll function
pollEndExecutionResult(transId);
@ -692,16 +699,14 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
restart(params.transId);
} else {
// Make ajax call to listen the database message
let baseUrl = url_for('debugger.execute_query', {
'trans_id': params.transId,
'query_type': 'continue',
});
api({
url: baseUrl,
url: getExecuteQueryUrl(params.transId, 'continue'),
method: 'GET',
})
.then(function (res) {
if (res.data.data.status) {
if (isBusy(res)) {
continueDebugger();
} else if (res.data.data.status) {
pollResult(params.transId);
} else {
pgAdmin.Browser.notifier.alert(
@ -723,12 +728,8 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
disableToolbarButtons();
// Make ajax call to listen the database message
let baseUrl = url_for('debugger.execute_query', {
'trans_id': params.transId,
'query_type': 'step_over',
});
api({
url: baseUrl,
url: getExecuteQueryUrl(params.transId, 'step_over'),
method: 'GET',
})
.then(function (res) {
@ -775,22 +776,21 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
// Get the local variable information of the functions and update the grid
const getLocalVariables = (transId) => {
// Make ajax call to listen the database message
let baseUrl = url_for(
'debugger.execute_query', {
'trans_id': transId,
'query_type': 'get_variables',
});
api({
url: baseUrl,
url: getExecuteQueryUrl(transId, 'get_variables'),
method: 'GET',
})
.then(function (res) {
if (res.data.data.status === 'Success') {
if (isBusy(res)) {
getLocalVariables(transId);
}
else if (isSuccess(res)) {
// Call function to update local variables
let variablesResult = res.data.data.result.filter((lvar) => {
return lvar.varclass == 'L';
});
eventBus.current.fireEvent(DEBUGGER_EVENTS.SET_LOCAL_VARIABLES, variablesResult);
enableToolbarButtons();
let parametersResult = res.data.data.result.filter((lvar) => {
return lvar.varclass == 'A';
@ -822,17 +822,14 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
const getStackInformation = (transId) => {
// Make ajax call to listen the database message
let baseUrl = url_for(
'debugger.execute_query', {
'trans_id': transId,
'query_type': 'get_stack_info',
});
api({
url: baseUrl,
url: getExecuteQueryUrl(transId, 'get_stack_info'),
method: 'GET',
})
.then(function (res) {
if (res.data.data.status === 'Success') {
if (isBusy(res)) {
getStackInformation(transId);
} else if (isSuccess(res)) {
// Call function to update stack information
eventBus.current.fireEvent(DEBUGGER_EVENTS.SET_STACK, res.data.data.result);
// Call function to create and update stack information
@ -867,6 +864,8 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
const updateInfo = (res, transId) => {
if (!params.directDebugger.debug_type && !params.directDebugger.first_time_indirect_debug) {
// Enable all the buttons as we got the results
enableToolbarButtons();
setLoaderText('');
editor.current.setActiveLine(-1);
clearAllBreakpoint(transId);
@ -940,16 +939,14 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
},
})
.then(function (res) {
if (res.data.data.status === 'Success') {
if (isSuccess(res)) {
// If no result then poll again to wait for results.
if (res.data.data.result == null || res.data.data.result.length == 0) {
pollResult(transId);
} else {
updateInfo(res, transId);
// Enable all the buttons as we got the results
enableToolbarButtons();
}
} else if (res.data.data.status === 'Busy') {
} else if (isBusy(res)) {
params.directDebugger.polling_timeout_idle = true;
checkDebuggerStatus(transId);
} else if (res.data.data.status === 'NotConnected') {
@ -967,16 +964,14 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
const stepInto = () => {
disableToolbarButtons();
// Make ajax call to listen the database message
let baseUrl = url_for('debugger.execute_query', {
'trans_id': params.transId,
'query_type': 'step_into',
});
api({
url: baseUrl,
url: getExecuteQueryUrl(params.transId, 'step_into'),
method: 'GET',
})
.then(function (res) {
if (res.data.data.status) {
if(isBusy(res)) {
stepInto();
} else if (res.data.data.status) {
pollResult(params.transId);
} else {
pgAdmin.Browser.notifier.alert(
@ -1005,7 +1000,9 @@ export default function DebuggerComponent({ pgAdmin, selectedNodeInfo, panelId,
data: data,
})
.then(function (res) {
if (res.data.data.status) {
if(isBusy(res)) {
onChangesLocalVarParameters(data);
} else if (res.data.data.status) {
// Get the updated variables value
getLocalVariables(params.transId);
// Show the message to the user that deposit value is success or failure

View File

@ -78,7 +78,6 @@ class CheckDebuggerForXssFeatureTest(BaseFeatureTest):
By.CSS_SELECTOR, "div[data-label='Debugging']")
).perform()
# time.sleep(2)
wait.until(EC.presence_of_element_located(
(By.CSS_SELECTOR, "li[data-label='Debug']")))
@ -87,7 +86,7 @@ class CheckDebuggerForXssFeatureTest(BaseFeatureTest):
# We need to check if debugger plugin is installed or not
try:
wait = WebDriverWait(self.page.driver, 2)
wait = WebDriverWait(self.page.driver, 10)
is_error = wait.until(EC.presence_of_element_located(
(By.XPATH, "//div[contains(@class,'MuiDialogTitle-root')]"
"//div[text()='Debugger Error']")
@ -120,6 +119,7 @@ class CheckDebuggerForXssFeatureTest(BaseFeatureTest):
wait.until(EC.presence_of_element_located(
(By.XPATH, "//span[contains(.,'Hello, pgAdmin4')]"))
)
self.page.click_element(
self.page.driver.find_elements(By.XPATH, "//button")[2]
)