c2f4daa739
Motivation: We need to ensure the tracked object can not be GC'ed before ResourceLeak.close() is called as otherwise we may get false-positives reported by the ResourceLeakDetector. This can happen as the JIT / GC may be able to figure out that we do not need the tracked object anymore and so already enqueue it for collection before we actually get a chance to close the enclosing ResourceLeak. Modifications: - Add ResourceLeakTracker and deprecate the old ResourceLeak - Fix some javadocs to correctly release buffers. - Add a unit test for ResourceLeakDetector that shows that ResourceLeakTracker has not the problems. Result: No more false-positives reported by ResourceLeakDetector when ResourceLeakDetector.track(...) is used.
475 lines
13 KiB
Java
475 lines
13 KiB
Java
/*
|
|
* Copyright 2015 The Netty Project
|
|
*
|
|
* The Netty Project licenses this file to you under the Apache License,
|
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at:
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
package io.netty.handler.codec.dns;
|
|
|
|
import io.netty.util.AbstractReferenceCounted;
|
|
import io.netty.util.ReferenceCountUtil;
|
|
import io.netty.util.ReferenceCounted;
|
|
import io.netty.util.ResourceLeakDetector;
|
|
import io.netty.util.ResourceLeakDetectorFactory;
|
|
import io.netty.util.ResourceLeakTracker;
|
|
import io.netty.util.internal.StringUtil;
|
|
import io.netty.util.internal.UnstableApi;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
|
|
|
/**
|
|
* A skeletal implementation of {@link DnsMessage}.
|
|
*/
|
|
@UnstableApi
|
|
public abstract class AbstractDnsMessage extends AbstractReferenceCounted implements DnsMessage {
|
|
|
|
private static final ResourceLeakDetector<DnsMessage> leakDetector =
|
|
ResourceLeakDetectorFactory.instance().newResourceLeakDetector(DnsMessage.class);
|
|
|
|
private static final int SECTION_QUESTION = DnsSection.QUESTION.ordinal();
|
|
private static final int SECTION_COUNT = 4;
|
|
|
|
private final ResourceLeakTracker<DnsMessage> leak = leakDetector.track(this);
|
|
private short id;
|
|
private DnsOpCode opCode;
|
|
private boolean recursionDesired;
|
|
private byte z;
|
|
|
|
// To reduce the memory footprint of a message,
|
|
// each of the following fields is a single record or a list of records.
|
|
private Object questions;
|
|
private Object answers;
|
|
private Object authorities;
|
|
private Object additionals;
|
|
|
|
/**
|
|
* Creates a new instance with the specified {@code id} and {@link DnsOpCode#QUERY} opCode.
|
|
*/
|
|
protected AbstractDnsMessage(int id) {
|
|
this(id, DnsOpCode.QUERY);
|
|
}
|
|
|
|
/**
|
|
* Creates a new instance with the specified {@code id} and {@code opCode}.
|
|
*/
|
|
protected AbstractDnsMessage(int id, DnsOpCode opCode) {
|
|
setId(id);
|
|
setOpCode(opCode);
|
|
}
|
|
|
|
@Override
|
|
public int id() {
|
|
return id & 0xFFFF;
|
|
}
|
|
|
|
@Override
|
|
public DnsMessage setId(int id) {
|
|
this.id = (short) id;
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public DnsOpCode opCode() {
|
|
return opCode;
|
|
}
|
|
|
|
@Override
|
|
public DnsMessage setOpCode(DnsOpCode opCode) {
|
|
this.opCode = checkNotNull(opCode, "opCode");
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public boolean isRecursionDesired() {
|
|
return recursionDesired;
|
|
}
|
|
|
|
@Override
|
|
public DnsMessage setRecursionDesired(boolean recursionDesired) {
|
|
this.recursionDesired = recursionDesired;
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public int z() {
|
|
return z;
|
|
}
|
|
|
|
@Override
|
|
public DnsMessage setZ(int z) {
|
|
this.z = (byte) (z & 7);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public int count(DnsSection section) {
|
|
return count(sectionOrdinal(section));
|
|
}
|
|
|
|
private int count(int section) {
|
|
final Object records = sectionAt(section);
|
|
if (records == null) {
|
|
return 0;
|
|
}
|
|
if (records instanceof DnsRecord) {
|
|
return 1;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
final List<DnsRecord> recordList = (List<DnsRecord>) records;
|
|
return recordList.size();
|
|
}
|
|
|
|
@Override
|
|
public int count() {
|
|
int count = 0;
|
|
for (int i = 0; i < SECTION_COUNT; i ++) {
|
|
count += count(i);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
@Override
|
|
public <T extends DnsRecord> T recordAt(DnsSection section) {
|
|
return recordAt(sectionOrdinal(section));
|
|
}
|
|
|
|
private <T extends DnsRecord> T recordAt(int section) {
|
|
final Object records = sectionAt(section);
|
|
if (records == null) {
|
|
return null;
|
|
}
|
|
|
|
if (records instanceof DnsRecord) {
|
|
return castRecord(records);
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
final List<DnsRecord> recordList = (List<DnsRecord>) records;
|
|
if (recordList.isEmpty()) {
|
|
return null;
|
|
}
|
|
|
|
return castRecord(recordList.get(0));
|
|
}
|
|
|
|
@Override
|
|
public <T extends DnsRecord> T recordAt(DnsSection section, int index) {
|
|
return recordAt(sectionOrdinal(section), index);
|
|
}
|
|
|
|
private <T extends DnsRecord> T recordAt(int section, int index) {
|
|
final Object records = sectionAt(section);
|
|
if (records == null) {
|
|
throw new IndexOutOfBoundsException("index: " + index + " (expected: none)");
|
|
}
|
|
|
|
if (records instanceof DnsRecord) {
|
|
if (index == 0) {
|
|
return castRecord(records);
|
|
} else {
|
|
throw new IndexOutOfBoundsException("index: " + index + "' (expected: 0)");
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
final List<DnsRecord> recordList = (List<DnsRecord>) records;
|
|
return castRecord(recordList.get(index));
|
|
}
|
|
|
|
@Override
|
|
public DnsMessage setRecord(DnsSection section, DnsRecord record) {
|
|
setRecord(sectionOrdinal(section), record);
|
|
return this;
|
|
}
|
|
|
|
private void setRecord(int section, DnsRecord record) {
|
|
clear(section);
|
|
setSection(section, checkQuestion(section, record));
|
|
}
|
|
|
|
@Override
|
|
public <T extends DnsRecord> T setRecord(DnsSection section, int index, DnsRecord record) {
|
|
return setRecord(sectionOrdinal(section), index, record);
|
|
}
|
|
|
|
private <T extends DnsRecord> T setRecord(int section, int index, DnsRecord record) {
|
|
checkQuestion(section, record);
|
|
|
|
final Object records = sectionAt(section);
|
|
if (records == null) {
|
|
throw new IndexOutOfBoundsException("index: " + index + " (expected: none)");
|
|
}
|
|
|
|
if (records instanceof DnsRecord) {
|
|
if (index == 0) {
|
|
setSection(section, record);
|
|
return castRecord(records);
|
|
} else {
|
|
throw new IndexOutOfBoundsException("index: " + index + " (expected: 0)");
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
final List<DnsRecord> recordList = (List<DnsRecord>) records;
|
|
return castRecord(recordList.set(index, record));
|
|
}
|
|
|
|
@Override
|
|
public DnsMessage addRecord(DnsSection section, DnsRecord record) {
|
|
addRecord(sectionOrdinal(section), record);
|
|
return this;
|
|
}
|
|
|
|
private void addRecord(int section, DnsRecord record) {
|
|
checkQuestion(section, record);
|
|
|
|
final Object records = sectionAt(section);
|
|
if (records == null) {
|
|
setSection(section, record);
|
|
return;
|
|
}
|
|
|
|
if (records instanceof DnsRecord) {
|
|
final List<DnsRecord> recordList = newRecordList();
|
|
recordList.add(castRecord(records));
|
|
recordList.add(record);
|
|
setSection(section, recordList);
|
|
return;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
final List<DnsRecord> recordList = (List<DnsRecord>) records;
|
|
recordList.add(record);
|
|
}
|
|
|
|
@Override
|
|
public DnsMessage addRecord(DnsSection section, int index, DnsRecord record) {
|
|
addRecord(sectionOrdinal(section), index, record);
|
|
return this;
|
|
}
|
|
|
|
private void addRecord(int section, int index, DnsRecord record) {
|
|
checkQuestion(section, record);
|
|
|
|
final Object records = sectionAt(section);
|
|
if (records == null) {
|
|
if (index != 0) {
|
|
throw new IndexOutOfBoundsException("index: " + index + " (expected: 0)");
|
|
}
|
|
|
|
setSection(section, record);
|
|
return;
|
|
}
|
|
|
|
if (records instanceof DnsRecord) {
|
|
final List<DnsRecord> recordList;
|
|
if (index == 0) {
|
|
recordList = newRecordList();
|
|
recordList.add(record);
|
|
recordList.add(castRecord(records));
|
|
} else if (index == 1) {
|
|
recordList = newRecordList();
|
|
recordList.add(castRecord(records));
|
|
recordList.add(record);
|
|
} else {
|
|
throw new IndexOutOfBoundsException("index: " + index + " (expected: 0 or 1)");
|
|
}
|
|
setSection(section, recordList);
|
|
return;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
final List<DnsRecord> recordList = (List<DnsRecord>) records;
|
|
recordList.add(index, record);
|
|
}
|
|
|
|
@Override
|
|
public <T extends DnsRecord> T removeRecord(DnsSection section, int index) {
|
|
return removeRecord(sectionOrdinal(section), index);
|
|
}
|
|
|
|
private <T extends DnsRecord> T removeRecord(int section, int index) {
|
|
final Object records = sectionAt(section);
|
|
if (records == null) {
|
|
throw new IndexOutOfBoundsException("index: " + index + " (expected: none)");
|
|
}
|
|
|
|
if (records instanceof DnsRecord) {
|
|
if (index != 0) {
|
|
throw new IndexOutOfBoundsException("index: " + index + " (expected: 0)");
|
|
}
|
|
|
|
T record = castRecord(records);
|
|
setSection(section, null);
|
|
return record;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
final List<DnsRecord> recordList = (List<DnsRecord>) records;
|
|
return castRecord(recordList.remove(index));
|
|
}
|
|
|
|
@Override
|
|
public DnsMessage clear(DnsSection section) {
|
|
clear(sectionOrdinal(section));
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public DnsMessage clear() {
|
|
for (int i = 0; i < SECTION_COUNT; i ++) {
|
|
clear(i);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
private void clear(int section) {
|
|
final Object recordOrList = sectionAt(section);
|
|
setSection(section, null);
|
|
if (recordOrList instanceof ReferenceCounted) {
|
|
((ReferenceCounted) recordOrList).release();
|
|
} else if (recordOrList instanceof List) {
|
|
@SuppressWarnings("unchecked")
|
|
List<DnsRecord> list = (List<DnsRecord>) recordOrList;
|
|
if (!list.isEmpty()) {
|
|
for (Object r : list) {
|
|
ReferenceCountUtil.release(r);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public DnsMessage touch() {
|
|
return (DnsMessage) super.touch();
|
|
}
|
|
|
|
@Override
|
|
public DnsMessage touch(Object hint) {
|
|
if (leak != null) {
|
|
leak.record(hint);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public DnsMessage retain() {
|
|
return (DnsMessage) super.retain();
|
|
}
|
|
|
|
@Override
|
|
public DnsMessage retain(int increment) {
|
|
return (DnsMessage) super.retain(increment);
|
|
}
|
|
|
|
@Override
|
|
protected void deallocate() {
|
|
clear();
|
|
|
|
final ResourceLeakTracker<DnsMessage> leak = this.leak;
|
|
if (leak != null) {
|
|
boolean closed = leak.close(this);
|
|
assert closed;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj) {
|
|
return true;
|
|
}
|
|
|
|
if (!(obj instanceof DnsMessage)) {
|
|
return false;
|
|
}
|
|
|
|
final DnsMessage that = (DnsMessage) obj;
|
|
if (id() != that.id()) {
|
|
return false;
|
|
}
|
|
|
|
if (this instanceof DnsQuery) {
|
|
if (!(that instanceof DnsQuery)) {
|
|
return false;
|
|
}
|
|
} else if (that instanceof DnsQuery) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return id() * 31 + (this instanceof DnsQuery? 0 : 1);
|
|
}
|
|
|
|
private Object sectionAt(int section) {
|
|
switch (section) {
|
|
case 0:
|
|
return questions;
|
|
case 1:
|
|
return answers;
|
|
case 2:
|
|
return authorities;
|
|
case 3:
|
|
return additionals;
|
|
}
|
|
|
|
throw new Error(); // Should never reach here.
|
|
}
|
|
|
|
private void setSection(int section, Object value) {
|
|
switch (section) {
|
|
case 0:
|
|
questions = value;
|
|
return;
|
|
case 1:
|
|
answers = value;
|
|
return;
|
|
case 2:
|
|
authorities = value;
|
|
return;
|
|
case 3:
|
|
additionals = value;
|
|
return;
|
|
}
|
|
|
|
throw new Error(); // Should never reach here.
|
|
}
|
|
|
|
private static int sectionOrdinal(DnsSection section) {
|
|
return checkNotNull(section, "section").ordinal();
|
|
}
|
|
|
|
private static DnsRecord checkQuestion(int section, DnsRecord record) {
|
|
if (section == SECTION_QUESTION && !(checkNotNull(record, "record") instanceof DnsQuestion)) {
|
|
throw new IllegalArgumentException(
|
|
"record: " + record + " (expected: " + StringUtil.simpleClassName(DnsQuestion.class) + ')');
|
|
}
|
|
return record;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private static <T extends DnsRecord> T castRecord(Object record) {
|
|
return (T) record;
|
|
}
|
|
|
|
private static ArrayList<DnsRecord> newRecordList() {
|
|
return new ArrayList<DnsRecord>(2);
|
|
}
|
|
}
|