Skip to content

Commit

Permalink
feat(config): expose force shutdown config
Browse files Browse the repository at this point in the history
  • Loading branch information
ysfscream committed Jan 2, 2025
1 parent a77036f commit 83e1da8
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 7 deletions.
24 changes: 24 additions & 0 deletions src/i18n/General.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,4 +259,28 @@ export default {
zh: '持久会话中消息保留时长,超期的消息不会发送到订阅者。',
en: 'The duration of message retention in a persistent session, messages that expire are not delivered to subscribers.',
},
enableForceShutdown: {
zh: '启用强制关闭',
en: 'Enable Force Shutdown',
},
enableForceShutdownDesc: {
zh: '启用 <code>force_shutdown</code>(强制关闭)功能,当进程堆内存或邮箱大小超过设定值时强制关闭进程',
en: 'Enable <code>force_shutdown</code> feature. Process will be forcibly shutdown when heap memory or mailbox size exceeds the set value',
},
maxHeapSize: {
zh: '最大堆内存',
en: 'Max Heap Size',
},
maxHeapSizeDesc: {
zh: '进程的最大堆内存大小。如果启用了强制关闭功能,超过此限制的进程将自动退出或被强制终止。进程消息队列(邮箱)中的消息也是堆的一部分。进程关闭可分为以下两种情况:\n- 进程在运行时主动检查当前堆大小,发现超出限制后主动退出\n- 底层调度系统在为进程执行垃圾回收后检查当前堆大小,发现超出限制后强制终止进程\n\n注意:上述两种情况产生的错误日志会有所不同。前者生成的日志类似于 <code>...errorContext: connection_shutdown, reason: #{max => 2097152, reason => proc_heap_too_large, value => 2787348}..</code>,后者生成的日志类似于 <code>...Context: maximum heap size reached...</code>。',
en: 'The maximum heap size of the process. If the force_shutdown is enabled, processes that exceed this limit will automatically exit or be forcibly killed. Messages in the process message queue (mailbox) are also part of the heap. The shutdown of a process can be divided into the following two situations:\n- The process actively checks the current heap size during its own operation, and actively exits after finding that it exceeds the limit.\n- The underlying scheduling system checks the current heap size after performing garbage collection for the process, and forcibly kills the process after finding that it exceeds the limit.\n\nNote: The Error logs generated by the above two will be different. The log generated by the former is similar to <code>...errorContext: connection_shutdown, reason: #{max => 2097152, reason => proc_heap_too_large, value => 2787348}..</code>, and the log generated by the latter is similar to <code>...Context: maximum heap size reached...</code>.',
},
maxMailboxSize: {
zh: '最大邮箱大小',
en: 'Max Mailbox Size',
},
maxMailboxSizeDesc: {
zh: 'EMQX 为每个客户端连接创建至少一个轻量级进程。每个进程都有自己的消息队列(邮箱)来保存来自其他进程(如 MQTT 消息)的消息,以便进程可以随时从消息队列(邮箱)中读取消息。如果系统繁忙或进程因繁忙的套接字而挂起,消息队列可能会积累大量消息。为避免过度使用内存,当进程的消息队列长度超过此值时,EMQX 将强制关闭该进程。',
en: 'EMQX creates at least one lightweight process for each client connection. Each process has its own message queue (aka mailbox) to hold messages from other processes (e.g. MQTT messages) so that the process can read messages from the message queue (mailbox) at any time. If the system is busy or the process hangs due to a busy socket (see <code>high_watermark</code>), the message queue can accumulate many messages. To avoid excessive memory usage, EMQX will force a process to shut down when the length of its message queue exceeds <code>max_mailbox_size</code>.',
},
}
4 changes: 4 additions & 0 deletions src/i18n/components.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,10 @@ export default {
zh: '会话持久化',
en: 'Durable Sessions',
},
'mqtt-force-shutdown': {
zh: '强制关闭',
en: 'Force Shutdown',
},
/* For Quick Panel Start */
'rule-create': {
zh: '创建规则',
Expand Down
5 changes: 5 additions & 0 deletions src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,11 @@ export const routes: Array<RouteRecordRaw> = [
name: 'mqtt-system-topic',
component: () => import('@/views/Config/BasicConfig/SystemTopics.vue'),
},
{
path: 'force-shutdown',
name: 'mqtt-force-shutdown',
component: () => import('@/views/Config/BasicConfig/ForceShutdown.vue'),
},
],
},
// log config
Expand Down
9 changes: 2 additions & 7 deletions src/types/config.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ComponentPublicInstance } from 'vue'
import { EmqxForceShutdown } from './schemas/configs.schemas'

export interface SubTabComponent extends ComponentPublicInstance {
index: number
Expand Down Expand Up @@ -189,7 +190,7 @@ export interface Zone {
mqtt: Mqtt
stats: Stats
flapping_detect: FlappingDetect
force_shutdown: ForceShutdown
force_shutdown: EmqxForceShutdown
conn_congestion: ConnCongestion
force_gc: ForceGc
overload_protection: OverloadProtection
Expand Down Expand Up @@ -238,12 +239,6 @@ export interface FlappingDetect {
ban_time: string
}

export interface ForceShutdown {
enable: boolean
max_message_queue_len: number
max_heap_size: string
}

export interface ConnCongestion {
enable_alarm: boolean
min_alarm_sustain_duration: string
Expand Down
155 changes: 155 additions & 0 deletions src/views/Config/BasicConfig/ForceShutdown.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<template>
<div class="force-shutdown app-wrapper">
<el-card class="allow-overflow">
<el-skeleton v-if="configLoading" :rows="12" animated />
<div class="schema-form" v-else>
<el-form
ref="forceShutdownForm"
class="configuration-form"
label-position="right"
require-asterisk-position="left"
hide-required-asterisk
:label-width="store.state.lang === 'zh' ? 138 : 210"
:model="forceShutdownConfig"
:validate-on-rule-change="false"
@keyup.enter="updateConfigData()"
>
<el-row>
<el-col :span="21" class="custom-col">
<el-form-item prop="enable">
<template #label>
<FormItemLabel
:label="tl('enableForceShutdown')"
:desc="tl('enableForceShutdownDesc')"
desc-marked
/>
</template>
<el-switch v-model="forceShutdownConfig.enable" />
</el-form-item>
</el-col>

<el-col :span="21" class="custom-col">
<el-form-item prop="max_heap_size">
<template #label>
<FormItemLabel
:label="tl('maxHeapSize')"
:desc="tl('maxHeapSizeDesc')"
desc-marked
/>
</template>
<InputWithUnit
v-model="forceShutdownConfig.max_heap_size"
:units="['KB', 'MB', 'GB']"
:disabled="!configEnable"
/>
</el-form-item>
</el-col>

<el-col :span="21" class="custom-col">
<el-form-item prop="max_mailbox_size">
<template #label>
<FormItemLabel
:label="tl('maxMailboxSize')"
:desc="tl('maxMailboxSizeDesc')"
desc-marked
/>
</template>
<CustomInputNumber
v-model.number="forceShutdownConfig.max_mailbox_size"
placeholder="1000"
:min="0"
:disabled="!configEnable"
/>
</el-form-item>
</el-col>

<el-col :span="24" class="btn-col">
<el-button type="primary" :loading="saveLoading" @click="updateConfigData()">
{{ $t('Base.saveChanges') }}
</el-button>
</el-col>
</el-row>
</el-form>
</div>
</el-card>
</div>
</template>

<script setup lang="ts">
import { getDefaultZoneConfigs, updateDefaultZoneConfigs } from '@/api/config'
import CustomInputNumber from '@/components/CustomInputNumber.vue'
import FormItemLabel from '@/components/FormItemLabel.vue'
import InputWithUnit from '@/components/InputWithUnit.vue'
import useConfFooterStyle from '@/hooks/useConfFooterStyle'
import useDataNotSaveConfirm from '@/hooks/useDataNotSaveConfirm'
import useI18nTl from '@/hooks/useI18nTl'
import { Zone } from '@/types/config'
import { ElMessage } from 'element-plus'
import { isEqual, cloneDeep } from 'lodash'
import { onMounted, ref, computed } from 'vue'
import { useStore } from 'vuex'
import { EmqxForceShutdown } from '@/types/schemas/configs.schemas'
const { t, tl } = useI18nTl('General')
const configLoading = ref(false)
const saveLoading = ref(false)
const store = useStore()
let rawData: any = undefined
const forceShutdownConfig = ref<EmqxForceShutdown>({
enable: false,
max_heap_size: '32MB',
max_mailbox_size: 1000,
})
const configEnable = computed(() => forceShutdownConfig.value?.enable === true)
const checkDataIsChanged = () => !isEqual(forceShutdownConfig.value, rawData)
useDataNotSaveConfirm(checkDataIsChanged)
const loadData = async () => {
try {
configLoading.value = true
const res = await getDefaultZoneConfigs()
forceShutdownConfig.value = res.force_shutdown
rawData = cloneDeep(forceShutdownConfig.value)
} catch (error) {
//
} finally {
configLoading.value = false
}
}
const updateConfigData = async () => {
saveLoading.value = true
try {
const zoneData: Zone = await getDefaultZoneConfigs()
zoneData.force_shutdown = forceShutdownConfig.value
await updateDefaultZoneConfigs(zoneData)
ElMessage.success(t('Base.updateSuccess'))
rawData = cloneDeep(forceShutdownConfig.value)
} catch (err) {
loadData()
} finally {
saveLoading.value = false
}
}
const { addObserverToFooter } = useConfFooterStyle()
onMounted(async () => {
await loadData()
addObserverToFooter()
})
</script>

<style lang="scss" scoped>
.custom-col {
padding-right: 0;
}
.btn-col {
margin-top: 16px;
text-align: right;
}
</style>

0 comments on commit 83e1da8

Please sign in to comment.