记DMA冲突引发的血案
11/11/2020 记DMA冲突引发的血案
环境
硬件使用Pixhawk fmuv2版本,软件为基于rtthread的飞控。
问题现象
电机控制线程使用UART6-DMA模式下从F4向F1发送数据时,开启日志记录,或使用和文件系统相关的命令时,UART6串口停止发送数据。在不开启SD卡相关记录和命令时,电机控制线程正常。
犯罪现场
将PWM使用的IO抽象为一个设备时(F4为主控芯片控制电机转速,是经过UART6串口发送给F1,再通过F1控制电机。所以将PWM抽象为一个设备,PWM的读写又操作了串口设备。后续有详细过程)
信号量初始为1
rt_sem_init(&iomcu_tx_pack_sem, "txpack", 1, RT_IPC_FLAG_FIFO);
对应的发送函数:
static rt_err_t send(uint8_t* buff, uint32_t size)
{rt_size_t bytes;if (rt_sem_take(&iomcu_tx_pack_sem, MSEC_TO_TICKS(20)) != RT_EOK){return -RT_EIO;}else{bytes = rt_device_write(serial_dev, 0, (const void *)buff, size);if(bytes != size)return -RT_ERROR;}return RT_EOK;
}
- 发送完成回调
rt_err_t iomcu_serial_tx_done(rt_device_t dev, void * buffer)
{rt_sem_release(&iomcu_tx_pack_sem);return RT_EOK;
}
具体现象
在记日志前,发送一次时取信号量,完成后释放信号量,每次发送都有回调。但是开启记日志后,发送后不会进入回调,也就不会释放信号量,导致下一次发送获取不到信号量,无法发送(其实看到这里就应该想到时SD卡的读取和UART6的收发底层配置冲突,但是CUBE软件误导了我,后面会说)。
排查过程
通过keil断点调试,发现发送回调是在logger.c中的
logger_start()
函数后就收不到了。具体是在open(file_name, O_WRONLY | O_APPEND | O_CREAT);
这个操作后。uint8_t logger_start(char* file_name, uint32_t log_period) {uint8_t res = 0;if(!mnt_init_complete()){Console.e(TAG, "err, file system is not init properly\n");return 1;}if(_logger_info.status == LOGGER_BUSY){Console.print("logger is busy, please first stop log\n");return 2;}if(logger_create_header(log_period>0 ? log_period : LOGGER_DEFAULT_PERIOD))return 3;/* create log file */int size;logger_fp = open(file_name, O_WRONLY | O_APPEND | O_CREAT);//具体就是在这一句if(logger_fp >= RT_EOK){//fres = f_write(&logger_fp, log_header_t, sizeof(LOG_HeaderDef)-sizeof(LOG_ElementInfoDef*), &bw);size = write(logger_fp, log_header_t, sizeof(LOG_HeaderDef)-sizeof(LOG_ElementInfoDef*));if(size == sizeof(LOG_HeaderDef)-sizeof(LOG_ElementInfoDef*)){//fres = f_write(&logger_fp, log_header_t->element_info, log_header_t->element_num*sizeof(LOG_ElementInfoDef), &bw);size = write(logger_fp, log_header_t->element_info, log_header_t->element_num*sizeof(LOG_ElementInfoDef));if(size!= log_header_t->element_num*sizeof(LOG_ElementInfoDef)){Console.e(TAG, "log header write fail:%d\n", size);res = 4;goto error;}rt_tick_t tick = log_period>0 ? log_period : LOGGER_DEFAULT_PERIOD;_logger_info.status = LOGGER_BUSY;_logger_info.last_record_time = 0;_logger_info.log_period = tick;/* start logger timer */rt_timer_control(&_timer_logger, RT_TIMER_CTRL_SET_TIME, &tick);rt_timer_start(&_timer_logger);Console.print("log file create successful, start to log... tick=%d\n", tick);}else{Console.e(TAG, "log header write fail\n");res = 4;}}else{Console.e(TAG, "log file create fail:%d\n", logger_fp);res = 4;}
继续进入
open()
函数查找问题- 猜想一:在进行文件系统的操作时,进行了临界区的上锁,关闭硬件中断后,无法响应发送完成中断。
- 猜想二:文件系统优先级较高,一直占用CPU资源。使得UART6的DMA写入第一次未完成,就进行了第二次写入。第一次写入前取了信号量,但是由于被第二次打断了,没有完成发送完成中断的回调函数释放信号量。
对于猜想二,我直接将文件系统的优先级调至比电机驱动线程低,但问题仍未解决。
对于猜想一:文件系统是使用互斥量上锁,防止其他进程中间打断的,并不影响硬件中断的响应。
void dfs_lock(void)
{rt_err_t result = -RT_EBUSY;while (result == -RT_EBUSY){result = rt_mutex_take(&fslock, RT_WAITING_FOREVER);}if (result != RT_EOK){RT_ASSERT(0);}
}
而在信号量获取或者释放过程中,的确进行了中断关闭,但是事件极短,在关闭期间丢掉了发送完成中断的几率很小。
rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time)
{register rt_base_t temp;struct rt_thread *thread;/* this function must not be used in interrupt even if time = 0 */RT_DEBUG_IN_THREAD_CONTEXT;/* parameter check */RT_ASSERT(mutex != RT_NULL);RT_ASSERT(rt_object_get_type(&mutex->parent.parent) == RT_Object_Class_Mutex);/* get current thread */thread = rt_thread_self();/* disable interrupt */temp = rt_hw_interrupt_disable(); //在这里关闭了中断,后面开启。RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(mutex->parent.parent)));RT_DEBUG_LOG(RT_DEBUG_IPC,("mutex_take: current thread %s, mutex value: %d, hold: %d\n",thread->name, mutex->value, mutex->hold));/* reset thread error */thread->error = RT_EOK;if (mutex->owner == thread){/* it's the same thread */mutex->hold ++;}else{__again:/* The value of mutex is 1 in initial status. Therefore, if the* value is great than 0, it indicates the mutex is avaible.*/if (mutex->value > 0){/* mutex is available */mutex->value --;/* set mutex owner and original priority */mutex->owner = thread;mutex->original_priority = thread->current_priority;mutex->hold ++;}else{/* no waiting, return with timeout */if (time == 0){/* set error as timeout */thread->error = -RT_ETIMEOUT;/* enable interrupt */rt_hw_interrupt_enable(temp);return -RT_ETIMEOUT;}else{/* mutex is unavailable, push to suspend list */RT_DEBUG_LOG(RT_DEBUG_IPC, ("mutex_take: suspend thread: %s\n",thread->name));/* change the owner thread priority of mutex */if (thread->current_priority < mutex->owner->current_priority){/* change the owner thread priority */rt_thread_control(mutex->owner,RT_THREAD_CTRL_CHANGE_PRIORITY,&thread->current_priority);}/* suspend current thread */rt_ipc_list_suspend(&(mutex->parent.suspend_thread),thread,mutex->parent.parent.flag);/* has waiting time, start thread timer */if (time > 0){RT_DEBUG_LOG(RT_DEBUG_IPC,("mutex_take: start the timer of thread:%s\n",thread->name));/* reset the timeout of thread timer and start it */rt_timer_control(&(thread->thread_timer),RT_TIMER_CTRL_SET_TIME,&time);rt_timer_start(&(thread->thread_timer));}/* enable interrupt */rt_hw_interrupt_enable(temp);/* do schedule */rt_schedule();if (thread->error != RT_EOK){/* interrupt by signal, try it again */if (thread->error == -RT_EINTR) goto __again;/* return error */return thread->error;}else{/* the mutex is taken successfully. *//* disable interrupt */temp = rt_hw_interrupt_disable();}}}}
更换思路
因为文件系统的实现极其复杂,并且是经过验证的,在这里出错的机率不大。所以放弃从上层向下,反过来从底层向上找。
DMA发送完成中断处理函数的入口
void UART6_DMA_TX_IRQHandler(void) {/* enter interrupt */rt_interrupt_enter();HAL_DMA_IRQHandler(&uart_obj[UART6_INDEX].dma_tx.handle);/* leave interrupt */rt_interrupt_leave(); }
在
dma_config.h
文件中#define UART6_DMA_TX_IRQHandler DMA2_Stream6_IRQHandler
在startup_stm32f427xx.s文件中加入中断向量表
DCD DMA2_Stream6_IRQHandler ; DMA2 Stream 6
进入HAL_DMA_IRQHandler()中断请求函数中查看
void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma) {uint32_t tmpisr;__IO uint32_t count = 0U;uint32_t timeout = SystemCoreClock / 9600U;/* calculate DMA base and stream number */DMA_Base_Registers *regs = (DMA_Base_Registers *)hdma->StreamBaseAddress;tmpisr = regs->ISR;/* Transfer Error Interrupt management ***************************************/if ((tmpisr & (DMA_FLAG_TEIF0_4 << hdma->StreamIndex)) != RESET){if(__HAL_DMA_GET_IT_SOURCE(hdma, DMA_IT_TE) != RESET){/* Disable the transfer error interrupt */hdma->Instance->CR &= ~(DMA_IT_TE);/* Clear the transfer error flag */regs->IFCR = DMA_FLAG_TEIF0_4 << hdma->StreamIndex;/* Update error code */hdma->ErrorCode |= HAL_DMA_ERROR_TE;}}/* FIFO Error Interrupt management ******************************************/if ((tmpisr & (DMA_FLAG_FEIF0_4 << hdma->StreamIndex)) != RESET){if(__HAL_DMA_GET_IT_SOURCE(hdma, DMA_IT_FE) != RESET) //开启日志记录后,会进入FIFO错误处理{/* Clear the FIFO error flag */regs->IFCR = DMA_FLAG_FEIF0_4 << hdma->StreamIndex;/* Update error code */hdma->ErrorCode |= HAL_DMA_ERROR_FE;}}/* Direct Mode Error Interrupt management ***********************************/....../* Half Transfer Complete Interrupt management ******************************/....../* Transfer Complete Interrupt management ***********************************/if ((tmpisr & (DMA_FLAG_TCIF0_4 << hdma->StreamIndex)) != RESET){ //开启日志记录后,发送完成标志位会置位,进入第一个if,但是后面判断中断使能时为disable,不会继续进行。 if(__HAL_DMA_GET_IT_SOURCE(hdma, DMA_IT_TC) != RESET){/* Clear the transfer complete flag */regs->IFCR = DMA_FLAG_TCIF0_4 << hdma->StreamIndex;if(HAL_DMA_STATE_ABORT == hdma->State){/* Disable all the transfer interrupts */hdma->Instance->CR &= ~(DMA_IT_TC | DMA_IT_TE | DMA_IT_DME);hdma->Instance->FCR &= ~(DMA_IT_FE);if((hdma->XferHalfCpltCallback != NULL) || (hdma->XferM1HalfCpltCallback != NULL)){hdma->Instance->CR &= ~(DMA_IT_HT);}/* Clear all interrupt flags at correct offset within the register */regs->IFCR = 0x3FU << hdma->StreamIndex;/* Process Unlocked */__HAL_UNLOCK(hdma);/* Change the DMA state */hdma->State = HAL_DMA_STATE_READY;if(hdma->XferAbortCallback != NULL){hdma->XferAbortCallback(hdma);}return;}if(((hdma->Instance->CR) & (uint32_t)(DMA_SxCR_DBM)) != RESET){/* Current memory buffer used is Memory 0 */if((hdma->Instance->CR & DMA_SxCR_CT) == RESET){if(hdma->XferM1CpltCallback != NULL){/* Transfer complete Callback for memory1 */hdma->XferM1CpltCallback(hdma);}}/* Current memory buffer used is Memory 1 */else{if(hdma->XferCpltCallback != NULL){/* Transfer complete Callback for memory0 */hdma->XferCpltCallback(hdma);}}}/* Disable the transfer complete interrupt if the DMA mode is not CIRCULAR */......}}
这里是当时最让人费解的地方:开启记录日志后,发送函数无法进入,但是FIFO错误标志位会置位,发送完成标志位也会置位,但是发送完成的回调不会调用,因为并没有开启发送完成使能。
先说FIFO错误标志位,在配置DMA的时候,就没有开启FIFO功能,怎么会出现错误标志位呢?
再看发送完成标志位置位,但是却没有使能?
再往上找,在哪里开启了发送完成使能呢?
HAL_StatusTypeDef HAL_USART_Transmit_DMA(USART_HandleTypeDef *husart, uint8_t *pTxData, uint16_t Size) {....../* Enable the USART transmit DMA stream */tmp = (uint32_t *)&pTxData;HAL_DMA_Start_IT(husart->hdmatx, *(uint32_t *)tmp, (uint32_t)&husart->Instance->DR, Size);...... }
HAL_StatusTypeDef HAL_DMA_Start_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength) {......if(HAL_DMA_STATE_READY == hdma->State){....../* Enable Common interrupts*/hdma->Instance->CR |= DMA_IT_TC | DMA_IT_TE | DMA_IT_DME;hdma->Instance->FCR |= DMA_IT_FE;......}...... }
也就是说在每次调用串口DMA发送函数时,配置完成后就就开启写入,开启写入时开启中断使能。**那么就更奇怪了!**只要使用发送函数,就会开启中断使能来响应发送完成函数啊,怎么会中断标志位置位,但是使能却关闭了呢?
解决
正在我百思不得其解的时候,白同学提醒了我,是不是还是硬件配置导致的冲突?!在之前之所以没有到最底层去查看UART6和SDIO的配置,就是因为我依赖了CUBE,觉得CUBE配置对了就不会错。
CUBE配置图如下,没有任何冲突。CUBE的检查机制也保证了不会有任何冲突,不然无法生成代码。
但是我还是去到底层查看了一下
SDIO
#define SDIO_BUS_CONFIG \{ \.Instance = SDIO, \.dma_rx.dma_rcc = RCC_AHB1ENR_DMA2EN, \.dma_tx.dma_rcc = RCC_AHB1ENR_DMA2EN, \.dma_rx.Instance = DMA2_Stream3, \.dma_rx.channel = DMA_CHANNEL_4, \.dma_rx.dma_irq = DMA2_Stream3_IRQn, \.dma_tx.Instance = DMA2_Stream6, \.dma_tx.channel = DMA_CHANNEL_4, \.dma_tx.dma_irq = DMA2_Stream6_IRQn, \}
UART6
#define UART6_DMA_TX_IRQHandler DMA2_Stream6_IRQHandler
#define UART6_TX_DMA_RCC RCC_AHB1ENR_DMA2EN
#define UART6_TX_DMA_INSTANCE DMA2_Stream6
#define UART6_TX_DMA_CHANNEL DMA_CHANNEL_5
#define UART6_TX_DMA_IRQ DMA2_Stream6_IRQn
果然!SDIO和UART6的发送共用的DMA2的Stream6!
那一切就合理了,在使用SDIO前,UART6可以正常的发送和响应中断,直到SDIO开启的一瞬间,也就是之前文件系统中的open()的一瞬间,DMA2_Stream6这个通道被SDIO抢走了。由于SDIO正在使用这个通道,DMA2_Stream6的中断位还会不断的被置位,但是由于UART6并没有发送,也就没有中断使能,所以无法响应这个中断。
PS:SDIO没有使用DMA2_Stream6中断,而是使用了SDIO中断
番外一
在调试过程中,为了测试是否是SD操作过程中丢失了某一次UART6的回调,导致信号量一直位0无法发送,结果将信号量调大,调至100。发现,打开日志记录后,信号量卡在92不变,发送和回调都不能进入。同时电机线程一直处于挂起状态,不再调用。后来在查找原因时发现了,这一现象的原因,涉及到串口发送的机制(数据队列)。
下面为F4主控芯片的电机线程发送PWM值到F1IO芯片,进而控制电机转速的全过程,封装层数较多。
电机驱动是一个单独的线程,每1MS进入一次。
void MotorTask_entry(void* parameter) {rt_err_t res;rt_uint32_t recv_set = 0;rt_uint32_t wait_set = FT_MOTORS_EVENT_LOOP;/* create event */res = rt_event_init(&EventMotorTask, "EFTM", RT_IPC_FLAG_FIFO);/* register timer event */rt_timer_init(&TimerMotorTask, "TFTM",MotorTask_TimeUpdate,RT_NULL,FT_MOTORS_MSEC_TO_TICKS(FT_MOTORS_TIME_MS),RT_TIMER_FLAG_PERIODIC | RT_TIMER_FLAG_SOFT_TIMER);rt_timer_start(&TimerMotorTask);while(1){res = rt_event_recv(&EventMotorTask, wait_set, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, &recv_set);if(res == RT_EOK){if(recv_set & FT_MOTORS_EVENT_LOOP){MotorTask_Output();}}}}
判断订阅的电机节点是否更新,更新则进行输出。
void MotorTask_Output() {//V2硬件电机PWM的值通过串口发送到IO芯片,所以耗时较大,大约4000微秒 #ifndef HIL_SIMULATIONfloat motors[8];//static uint32_t start_us = 0;//start_us = time_nowUs();static uint32_t NavNow = 0;if(itc_poll(motors_node_t)) {MotorTask_SwitchMotorsOutput();itc_copy(ITC_ID(MOTOR_THROTTLE), motors_node_t, motors);rt_device_write(pMotorDevice, MOTOR_CH_ALL, motors, motorNum); /*if (time_nowMs() - NavNow > 500) {NavNow = time_nowMs();char val[20] = {0};sprintf(val, "%d\n", time_nowUs() - start_us);rt_kprintf("%s",val);} */} #endif }
PWM输出被抽象为一个设备叫做"motor",调用设备在初始画的时候注册的写函数
static rt_size_t _motor_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) {struct rt_device_motor *motor = (struct rt_device_motor *)dev;static float throttle_dc[8];float throttle;Motor_Chan_Info *chan_info = (Motor_Chan_Info*)dev->user_data;if(size > chan_info->max_output_chan_num){Console.e(TAG, "unsupport motor num:%ld\n", size);return 0;}/* set motor throttle */for(uint8_t i = 0 ; i < size ; i++){if(pos & (1<<i)){throttle = *((float*)buffer+i);constrain(&throttle, 0.0f, 1.0f);throttle_dc[i] = MOTOR_MIN_DC + throttle * (MOTOR_MAX_DC - MOTOR_MIN_DC);}}motor->ops->pwm_write(dev, (uint8_t)pos, throttle_dc);return size; }
"motor"设备的写函数调用了send_package进行数据打包和发送。过程如下:
- 在堆中动态申请了一片内存来存放数据
- 申请了一片内存存放打包好的待发送
- 数据打包
- 调用"uart6"设备的写函数
- 释放内存
uint8_t send_package(uint8_t cmd, uint8_t* data, uint16_t len) {Package_Def pack;SendPackage_Def send_pack;if(!make_package(data , cmd , len , &pack)){return 0;}package2sendpack(pack , &send_pack);send(send_pack.send_buff, send_pack.buff_size);free_pack(&pack);free_sendpack(&send_pack);return 1; }
动态申请内存主要是为了多个线程调用这个公用函数时,可以使数据不被覆盖。
static rt_err_t send(uint8_t* buff, uint32_t size) {rt_size_t bytes;if (rt_sem_take(&iomcu_tx_pack_sem, MSEC_TO_TICKS(20)) != RT_EOK){return -RT_EIO;}else{bytes = rt_device_write(serial_dev, 0, (const void *)buff, size);if(bytes != size)return -RT_ERROR;}return RT_EOK; }
使用信号量保证上次发送完成,调用"uart6"设备的写函数。
引入了数据队列,初始化数据队列长度为8。所以在发送时,先将数据排在队尾,判断队列是否满,如果满则将线程挂起,否则发送一次数据。
这里也就是为什么之前将信号量设为100的时候,开启SD卡后变为92。因为等待时间设的RT_WAITING_FOREVER
,队列的出列是在发送完成的回调函数完成的,所以队满后电机线程会一直被挂起等待信号量。队列大小为8,等于说日志开启后,又发送了八次,但是每次都不能回调,导致队列满,线程挂起,无法发送,所以信号量定格在8。
rt_inline int _serial_dma_tx(struct rt_serial_device *serial, const rt_uint8_t *data, int length)
{rt_base_t level;rt_err_t result;struct rt_serial_tx_dma *tx_dma;tx_dma = (struct rt_serial_tx_dma*)(serial->serial_tx);result = rt_data_queue_push(&(tx_dma->data_queue), data, length, RT_WAITING_FOREVER);if (result == RT_EOK){level = rt_hw_interrupt_disable();if (tx_dma->activated != RT_TRUE){tx_dma->activated = RT_TRUE;rt_hw_interrupt_enable(level);/* make a DMA transfer */serial->ops->dma_transmit(serial, (rt_uint8_t *)data, length, RT_SERIAL_DMA_TX);}else{rt_hw_interrupt_enable(level);}return length;}else{rt_set_errno(result);return 0;}
}
调用底层的HAL库DMA发送函数
static rt_size_t stm32_dma_transmit(struct rt_serial_device *serial, rt_uint8_t *buf, rt_size_t size, int direction) {struct stm32_uart *uart;RT_ASSERT(serial != RT_NULL);RT_ASSERT(buf != RT_NULL);uart = rt_container_of(serial, struct stm32_uart, serial);if (size == 0){return 0;}if (RT_SERIAL_DMA_TX == direction){if (HAL_UART_Transmit_DMA(&uart->handle, buf, size) == HAL_OK){return size;}else{return 0;}}return 0; }
调用中断回调函数(嵌套层数较多)
中断服务入口函数
DMA2_Stream6_IRQHandler
-> 用户重写弱函数UART6_DMA_TX_IRQHandler
-> HAL库提供中断处理函数HAL_DMA_IRQHandler
-> 调用了用户配置的回调函数HAL_UART_TxCpltCallback
-> 判断DMA是否发送完成(网友说会出现DMA发送完成中断产生,但是最后一个自己数据未发送完成的情况)_dma_tx_complete
-> 终极回调函数rt_hw_serial_isr
终极回调函数中关注两件事1、将发送完成的数据弹出数据队列 2、调用设备的完成回调(释放信号量)
void rt_hw_serial_isr(struct rt_serial_device *serial, int event) {......rt_data_queue_pop(&(tx_dma->data_queue), &last_data_ptr, &data_size, 0);/* invoke callback */if (serial->parent.tx_complete != RT_NULL){serial->parent.tx_complete(&serial->parent, (void*)last_data_ptr);}}
记DMA冲突引发的血案相关推荐
- ”一个馒头引发的血案“|记Mybatis之BindingException异常的产生及解决过程
一. 业务场景 前几天壹哥带学生做一个项目,需要更新数据库中的车辆信息表,具体需求是要根据指定车辆的设备id(编号和设备ID均非主键)来更新车辆信息.壹哥要求学生们用Mybatis进行实现,所以就在对 ...
- Windows10记一次去掉桌面图标箭头引发的血案:该文件没有与之关联的应用来执行操作,请安装一个程序,若已安装程序,请在默认程序控制面板中创建关联。
前两天我的Windows10系统升级,这次升级试一次大型升级,从1803版本升级到1903版本,本以为升级系统能给我带来不一样的体验,可是没有--升级之后我桌面图标左下方又出现了小箭头,对于我这种强迫 ...
- 第三方账号登陆的过程及由此引发的血案
72agency · 2014/03/19 10:40 0x00 前言 第三方账号登陆也就是当你没有A网站的注册账号时,你可以使用该与A网站合作的第三方账号登陆A,在大多数情况下你会立即拥有与你第三方 ...
- 波涛汹涌的黄金甲,一碗中药引发的血案!
严重声明:网路转载 主要情节: 父王(周润发)说母后(巩利)身体虚寒,需要每天定时服用亲自配置的中药,已服用了几十年.而父王早就知道了母后和太子元祥(刘烨)之间的苟且之事,远征回宫后在其中药中加入一味 ...
- mysql backlog_一次优化引发的血案
前些天一个Nginx+PHP项目上线后遭遇了性能问题,于是打算练练手,因为代码并不是我亲自写的,所以决定从系统层面入手看看能否做一些粗线条的优化. 首先,我发现服务的Backlog设置过小,可以通过s ...
- 一个普通ERROR 1135 (HY000)错误引发的血案:
一个普通ERROR 1135 (HY000)错误引发的血案: 今天接到测试人员反应,测试环境前端应用程序无连接mysql数据库,登录mysql服务器,查看错误日志,发现有如下报错: 点击(此处)折叠或 ...
- 一次 Druid 连接池泄露引发的血案!
最近某个应用程序老是卡,需要重启才能解决问题,导致被各种投诉,排查问题是 Druid 连接池泄露引发的血案.. 异常日志如下: ERROR - com.alibaba.druid.pool.GetCo ...
- 线上 CPU100% 异常案例:一个正则表达式引发的血案
前几天线上一个项目监控信息突然报告异常,上到机器上后查看相关资源的使用情况,发现 CPU 利用率将近 100%.通过 Java 自带的线程 Dump 工具,我们导出了出问题的堆栈信息. 我们可以看到所 ...
- 一场由过滤器Filter引发的血案
一场由过滤器Filter引发的血案 事件起因 本来应该是下图的登录界面 变成了这样 What's the fuck????? 抓狂 原因 解决方法: 在过滤器中给资源文件开个绿色通道
最新文章
- 具有不同字体的列表框
- 记一次Oracle数据故障排除过程
- Algorithm Course Review(7.1)
- SpringMVC拦截器HandlerInterceptor原理及使用
- docker编译Linux内核,c – 如何编译在docker中使用内核函数的C代...
- 程序员 rs编码_为什么声明性编码使您成为更好的程序员
- 苹果要悄悄对这个产品动手了?你们最期盼的NFC功能也要来?
- 论文笔记_S2D.31_2015-CVPR_对单张图像进行统一的深度和语义预测
- 【大数据分析】Spark SQL查询:使用SQL命令
- 上下文无关文法的组成
- 一文盘点目前免费的云服务器
- 《Gpu Gems》《Gpu Pro》《Gpu Zen》系列读书笔记
- 【Memcached】分布式内存对象缓存系统
- linux网卡ens160显示不出来,修改Centos7的网卡名称ens160、eno192改为eth0、eth1
- 手写vue3源码——reactive, effect ,scheduler, stop 等
- print spooler打印服务启动后,自动停止的解决方法。
- HTML5 table表格合并单元格和合并边框
- 如何将小车标注在百度地图上并且设置车头方向
- 【欧拉计划第 5 题】最小公倍数 Smallest multiple
- C语言图形编程(绘图函数部分),C语言图形编程(三、绘图函数-02)12